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.Documentation Index
Fetch the complete documentation index at: https://docs.wellapp.ai/llms.txt
Use this file to discover all available pages before exploring further.
| Naming | Value |
|---|---|
| Object | Task |
Resource type (JSON:API type) | task |
| Collection / records root | tasks |
| REST base | /v1/tasks |
| Entity class | Task |
API operations
| Operation | Method & path | Status |
|---|---|---|
| List | GET /v1/tasks | ✅ Implemented |
| Retrieve | GET /v1/tasks/{id} | ✅ Implemented |
| Create | POST /v1/tasks | 🟡 Planned |
| Update | PATCH /v1/tasks/{id} | 🟡 Planned |
| Delete | DELETE /v1/tasks/{id} | 🟡 Planned |
Data model
Attributes
| Field | Type | Required | Constraints | Allowed values | Description |
|---|---|---|---|---|---|
| task_id | string, UUID, system | ✅ Yes | unique; generated by gen_random_uuid() on INSERT | — | Public stable identifier for the task. Never changes after creation. |
| title | string | ✅ Yes | text; no length limit enforced at DB level | — | Short human-readable label for the task, shown in the UI and chat cards. |
| description | string | ⚪ No | text; nullable | — | Optional longer prose explanation of what the task requires and why. |
| status | string (enum) | ✅ Yes | default = ‘open’; backed by native PG enum task_status_enum; partial unique index uniq_tasks_active_provider_company fires only on status IN (‘open’,‘ice_log’,‘blocked’) | ice_log, open, in_progress, done, blocked, cancelled, archived | Lifecycle state of the task. ice_log = surfaced but not yet actionable; archived = closed but kept for audit. |
| executor_type | string (enum) | ✅ Yes | NOT NULL; backed by native PG enum executor_type_enum | human, system | Whether the task is intended for a human actor or executed entirely by the system/agent pipeline. |
| source | string (enum) | ✅ Yes | default = ‘user’; backed by native PG enum task_source_enum | user, agent | Whether the task was created by a human user or autonomously by an AI agent. |
| priority | string (enum) | ⚪ No | nullable; default = ‘medium’; backed by native PG enum task_priority_enum | low, medium, high, critical | Urgency level of the task, used to sort and filter the task list in the UI. |
| confidence_score | number (decimal) | ⚪ No | nullable; columnType numeric(3,2); range 0.00-1.00 | — | Agent-assigned confidence that the task is relevant and actionable for this workspace. Higher means more certain. |
| token_reward | integer | ⚪ No | nullable; integer | — | Gamification reward tokens awarded to the user upon completing this task. Sourced from the associated TaskTemplate or set by the pipeline. |
| due_date | datetime | ⚪ No | nullable; no DB-level CHECK | — | Optional deadline after which the task is considered overdue. Displayed in the task card and used for sorting. |
| visible_date | datetime | ⚪ No | nullable; no DB-level CHECK | — | Date before which the task should not be surfaced to the user (deferred visibility). Used by the scoring pipeline to schedule tasks for future monthly-close cycles. |
| references | jsonb (array of TaskReference objects) | ✅ Yes | defaults to []; GIN index idx_tasks_references_gin (jsonb_path_ops) for fast dedup lookups | Array of { type: invoice|company|person|document|transaction|provider|month, id: string, label: string } | Polymorphic reference list linking the task to one or more domain entities. Drives context in the chat card and CTA routing. The first provider-typed entry is denormalized into provider_ref_id; the first company-typed entry into company_ref_id. |
| provider_ref_id | string | ⚪ No | nullable; varchar(36); denormalized from references[0 where type=‘provider’].id; participates in 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 both ref_ids NOT NULL AND parent_task_pk IS NULL | — | Denormalized first provider reference id extracted from the references array. Populated at task creation by TaskService.createTask. Enables O(1) dedup guard preventing duplicate root scoring tasks for the same workspace x provider x company triplet. |
| company_ref_id | string | ⚪ No | nullable; varchar(36); denormalized from references[0 where type=‘company’].id; co-participant in partial unique index uniq_tasks_active_provider_company | — | Denormalized first company reference id extracted from the references array. Populated at task creation by TaskService.createTask alongside provider_ref_id. |
| history | jsonb (array of TaskHistoryEntry objects) | ✅ Yes | defaults to [] | Array of { action: created|status_changed|assigned|comment, at: ISO8601, by?: string, by_type: human|system, from?: string, to?: string, detail?: string } | Append-only audit log of state transitions and comments on this task. Each entry records who did what and when. |
| plan_meta | jsonb (discriminated union on ‘kind’) | ⚪ No | nullable. 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. | kind: plan | step | scoring | mail-connect | connect-extension | connect-tools-parent | subworkspace-candidate | Optional metadata blob whose shape is discriminated by the ‘kind’ field. plan = agentic plan phases. step = individual step within a plan. scoring = provider-scoring signals and CTA route. mail-connect = connect-email fallback gate. connect-extension = Chrome extension install sub-task. connect-tools-parent = lazy parent for provider-connection subtasks. subworkspace-candidate = WAS-projection, detection signals, and dedup_key for child workspace discovery. |
| score | number (decimal) | ⚪ No | nullable; columnType numeric(8,2) | — | Computed relevance or urgency score assigned by the scoring pipeline (0-1000+ scale). Used to order tasks in the UI surface. |
| done_at | datetime | ⚪ No | nullable; no DB trigger; set by service layer | — | Timestamp when the task transitioned to status=done. Set by the service layer on completion. |
| conversation_thread_id | string, UUID | ⚪ No | nullable; UuidType; soft reference only (no FK constraint) | — | UUID of the chat conversation thread associated with this task. Allows the AI to link back to the thread context. No FK cascade — deleting the conversation does not affect the task. |
| created_at | datetime, system | ✅ Yes | set on INSERT via MikroORM onCreate lifecycle; not nullable | — | Timestamp when the task row was created. |
| updated_at | datetime, system | ⚪ No | set on INSERT (onCreate) and updated on every UPDATE (onUpdate) via MikroORM lifecycle | — | Timestamp of the last modification to this task row. |
| deleted_at | datetime, system | ⚪ No | nullable; soft-delete sentinel; all active queries filter deleted_at IS NULL; both partial unique indexes include deleted_at IS NULL in their WHERE clause so hard-deletes release dedup slots | — | Soft-delete timestamp. When set, the task is logically deleted and excluded from all active queries. |
Relationships
| Name | Type | Required | Description |
|---|---|---|---|
| workspace | to-one (workspace) | ✅ Yes | The workspace this task belongs to. deleteRule = ‘cascade’ — deleting the workspace hard-deletes all its tasks. Every query must scope to this relationship. Composite index idx_tasks_workspace_status covers (workspace_pk, status) for efficient status-filtered list queries. |
| template | to-one (task_template) | ⚪ No | Optional reference to the TaskTemplate that spawned or defines this task (title, markdown content, action type, token reward). deleteRule = ‘set null’ — deleting a template leaves tasks intact but unlinked. Template-less tasks are common for agent-generated or platform-seeded rows. |
| parent_task | to-one (task) | ⚪ No | Self-referential parent for hierarchical task trees. A root task has parent_task = null. deleteRule = ‘set null’ — deleting a parent orphans its children rather than cascade-deleting them. The partial unique index uniq_tasks_active_provider_company only applies to root tasks (parent_task_pk IS NULL). |
| subtasks | to-many (task) | — | Inverse of parent_task. Collection of child tasks nested under this task. The composite composite_subtasks_list (display_type: relation_list, sort_proxy: subtasks_aggregate.min.created_at) in composites.yml surfaces subtask task_id, title, status, and due_date as a relation_list cell in the records table. |
| assigned_to | to-one (people) | ⚪ No | The Person to whom the task is assigned for action. deleteRule = ‘set null’. The composite assigned_to.composite_avatar_fullname (display_type: people_avatar_name) in composites.yml surfaces person_id, avatar URL, and full_name as a composite cell in the records table. |
| created_by | to-one (people) | ⚪ No | The Person who created the task, when created by a human. Null for agent-created or system-seeded tasks. deleteRule = ‘set null’. |
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
apps/api/src/database/entities/Task.ts · domain: workspace · tier: Activity