ADR-013: Task tracking is a plugin capability; "Ever Works Task Tracker" is the first-party plugin storing tasks in platform DB
Status
Accepted — 2026-05-25. Operator instruction during round 6 review of PR #1017. Supersedes the relevant part of ADR-006 (Tasks are no longer "core not plugin").
Date
2026-05-25
Context
ADR-006 (round 1) declared Tasks core, with a reserved-but-not-consumed task-tracker plugin capability for future Linear/JIRA/GitHub integrations. Round 6 review reversed this:
"About 006-agents-skills-tasks-as-core-not-plugins.md — I think actually for Task tracking, that's a good idea to implement it as plugin in the first place! I.e. we can call it 'Ever Works Task Tracker' plugin and implement it and later will be 'Linear Task Tracker', 'JIRA Task Tracker', 'GitHub Task Tracker' plugins etc that can be enabled to replace our own plugin in each Tenant etc. So no, I don't agree with ADR, while tasks are core to the platform, actually task TRACKING (storage, etc) should be a plugin."
Crucial nuance from the operator: the Task concept is still core; what becomes pluggable is the TRACKING — storage, sync, list/create/update operations.
Decision
Task TRACKING is a plugin capability. The platform defines plugin category task-tracker with the contract ITaskTrackerPlugin. The first-party plugin "Ever Works Task Tracker" is the default task-tracker, storing tasks in platform DB tables (the schema designed in features/task-tracking/plan.md).
Future community plugins can implement the same capability — e.g.:
- Linear Task Tracker — proxies create/list/update/delete to Linear's API.
- JIRA Task Tracker — same for Jira Cloud.
- GitHub Issues Task Tracker — backs tasks with GitHub Issues.
- Asana Task Tracker — same for Asana.
A tenant has exactly one active task-tracker plugin at a time (unlike Skills which support union). Switching trackers is a deliberate user action with a migration UI (export from old → import to new).
What stays core
- The Task CONCEPT (its entities, state machine, relationships to Agents/Missions/Ideas/Works) is core.
- The Tasks UI —
/taskspage, Task detail, Kanban view, Task tabs on Work/Mission/Idea pages. Plugin-agnostic. - The state-machine logic (
TaskTransitionService, blocker cycle detection, approver gating). - The Agent integration (a Task with an Agent assignee dispatches
agent-task-execute). - The DTO contracts in
@ever-works/contracts.
What moves into the plugin
- Storage — the
tasks/task_assignees/task_chat_messages/etc. tables become the "Ever Works Task Tracker" plugin's storage. They're still TypeORM entities inpackages/agent/src/entities/, but conceptually owned by the plugin. - CRUD operations —
create,read,update,delete,transitiongo throughITaskTrackerPlugin. - Chat operations —
postMessage,listMessagesvia the plugin. - Search / filter / list — paginated list with filters.
ITaskTrackerPlugin contract
// packages/plugin/src/contracts/capabilities/task-tracker.interface.ts
export interface TaskDto {
id: string;
slug: string;
title: string;
description?: string;
status: TaskStatus;
priority: TaskPriority;
labels: string[];
scope: { workId?: string; missionId?: string; ideaId?: string };
assignees: { type: 'user' | 'agent'; id: string }[];
// ... full Task shape
}
export interface ITaskTrackerPlugin extends IPlugin {
readonly providerName: string;
// CRUD
listTasks(options: ListTasksOptions): Promise<{ tasks: TaskDto[]; total: number }>;
getTask(id: string): Promise<TaskDto | null>;
createTask(input: CreateTaskInput, context: TaskContext): Promise<TaskDto>;
updateTask(id: string, patch: UpdateTaskInput, context: TaskContext): Promise<TaskDto>;
deleteTask(id: string, context: TaskContext): Promise<void>;
// State machine
transitionTask(id: string, to: TaskStatus, context: TaskContext): Promise<TaskDto>;
// Assignees / reviewers / approvers (mutations)
addAssignee(id: string, assignee: { type: 'user' | 'agent'; id: string }): Promise<void>;
removeAssignee(id: string, assigneeId: string): Promise<void>;
// ... reviewers, approvers, blockers, relations
// Chat
listChat(taskId: string, options: { limit: number; cursor?: string }): Promise<{ messages: ChatMessageDto[] }>;
postChat(taskId: string, body: PostChatInput, context: TaskContext): Promise<ChatMessageDto>;
// Attachments
listAttachments(taskId: string): Promise<AttachmentDto[]>;
attachUpload(taskId: string, uploadId: string): Promise<void>;
// Capability flags — for trackers that don't support certain features
readonly supportsSubTasks: boolean;
readonly supportsBlockers: boolean;
readonly supportsApprovers: boolean;
readonly supportsChat: boolean;
readonly supportsAgentAssignees: boolean; // Linear/Jira/GH likely false
}
TasksFacadeService (new)
Resolves the active task-tracker plugin for the user/work scope; delegates all task operations. The UI never talks to a plugin directly — always to the facade.
When supportsAgentAssignees = false (third-party trackers may not allow Agent assignees), the platform stores the Agent assignment in a side-table task_agent_assignments(externalTaskId, agentId) so Agent execution still works while the tracker stays clean.
"Ever Works Task Tracker" — the first-party plugin
Lives at packages/plugins/everworks-task-tracker/. package.json everworks.plugin block:
{
"id": "everworks-task-tracker",
"name": "Ever Works Task Tracker",
"category": "task-tracker",
"capabilities": ["task-tracker"],
"defaultForCapabilities": ["task-tracker"],
"visibility": "public",
"settingsSchema": {
/* (mostly empty — uses platform DB) */
}
}
Implementation:
- All CRUD operations write to the existing
tasks/task_*tables via the existing repositories (which live inpackages/agent/src/database/repositories/). - All capability flags are
true(the only tracker that supports the full feature set). - Bundles the Task templates loaded from
ever-works/tasks(per ADR-014) for the "+ New Task from template" flow.