Skip to main content

Implementation Reuse Map — Agents / Skills / Tasks

Status: Draft · 2026-05-25 Audience: Lead engineer + reviewers. Every new piece of Agents/Skills/Tasks scope is mapped to the existing platform asset it reuses. The goal: prove that the "minimal new code" claim holds, and surface exactly what's genuinely new.

Why this doc. Round 1-3 specs say "reuse the X facade" / "extend Y pattern" repeatedly. This doc makes it concrete: name the file, name the verb, name the test fixture. So an implementer can scan one table and find their handhold.


1. Entities — map to reuse pattern

| New entity | Reuse pattern | Genuinely new | | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | ---- | ------- | | Agent | Same TypeORM shape as Mission (mission.entity.ts); polymorphic owner cols + @Index('uq_..._userId_slug') unique. | Five inline TEXT cols for DB-only tenant file storage. | | AgentRun | Same as WorkGenerationHistory (work-generation-history.entity.ts) — status enum, started/finished timestamps, error, summary, optional triggerRunId. | The triggerKind discriminator (heartbeat | task | chat). | | AgentRunLog | Verbatim copy of WorkAgentRunLog (work-agent-run-log.entity.ts) — only FK renamed (runId → agent_runs.id). | Nothing. | | AgentBudget | Polymorphic owner already lives on WorkBudget (work-budget.entity.ts) — extend BudgetOwnerType enum in _types.ts with AGENT + TASK. | Nothing schema-side; values only. | | AgentMembership | Same shape as the polymorphic (ownerType, ownerId) columns already used by WorkBudget. | The unique index on (agentId, targetType, targetId). | | Skill | Closest analog is Template (template.entity.ts). Owned by the "Ever Works Skills" plugin (ADR-012) — entity lives in packages/agent/src/entities/ but writes go through SkillsFacadeService → ISkillsProviderPlugin. | Frontmatter jsonb + content hash. | | SkillBinding | New shape — no exact precedent. Closest is WorkAdvancedPrompts (one-per-Work). Multiplicity differs (many-to-many). | The targetType polymorphic + injectIntoAgent / injectIntoGenerator booleans. | | Task | Closest analog is WorkProposal (work-proposal.entity.ts) — also has status enum, priority-like field, slug. Owned by the "Ever Works Task Tracker" plugin (ADR-013); third-party task-tracker plugins (Linear / Jira / GitHub) replace storage but use the same DTO shape. | The state machine + parentTaskId recursion + requireAllApprovers. | | TaskAssignee | Polymorphic shape mirrors WorkBudget's owner cols. (assigneeType, assigneeId). | Nothing. | | TaskBlocks | Self-referential join — same shape as task_relations. Cycle detection helper is the only new helper. | The cycle detector (runs recursive CTE). | | TaskRelations | Same as the existing template-customization back-pointer pattern. | The kind enum field. | | TaskChatMessage | Closest analog is ConversationMessage (conversation-message.entity.ts) — role, content, FK to parent. | The authorType polymorphic + mentions jsonb + attachments jsonb. | | TaskAttachment | Reuses work_knowledge_upload.id FK — no new upload pipeline. | Nothing. | | TaskWatcher | Same as the existing join-table pattern (e.g. work_member). | Nothing. | | TaskKbMention | Same. | Nothing. | | UserTaskCounter | Single-row-per-user atomic counter; no direct analog but trivial. | The atomic increment SQL. |

Genuinely new entity rows: 0. Every new entity is a variation of an existing shape.


2. Services — map to reuse

New serviceReuseGenuinely new logic
AgentServiceCRUD + scope validation — analogous to MissionService.Scope-cascade validator (in validateScopeOwnership()).
AgentRunServiceRun lifecycle + status transitions — analogous to WorkGenerationService.The 3-trigger dispatch (heartbeat / task / chat) + prompt assembly orchestration.
AgentFileServiceBranches between GitFacadeService.commit() and inline DB write.The branching logic + secret-scan pre-write.
AgentToolServiceTool registration shape from agent-pipeline / claude-code plugins.The tool catalog + per-tool permission gate.
AgentBudgetServicePolymorphic delegation to existing BudgetService.summarizeForOwner({ownerType: AGENT}) — zero new aggregation.Nothing.
AgentScheduleDispatcherServiceVerbatim pattern of WorkScheduleDispatcherService — CAS-claim via markRunDispatched. (research)Different filter (Agents not Schedules); identical claim logic.
PromptAssemblerServiceReads from existing WorkAdvancedPrompts repo + Skill resolver + activity-log queries.The 11-segment assembly recipe (the one piece of code without a clear precedent).
SkillsFacadeService + "Ever Works Skills" plugin (per ADR-012)Plugin clones ever-works/skills repo (per ADR-014). Facade resolves enabled skills-provider plugins and dedupes by slug.ISkillsProviderPlugin contract; facade dedup logic.
SkillBindingServiceStandard CRUD; reuses repository pattern.The resolveActive() priority-sorted resolver.
SkillFileServiceSame branch as AgentFileService (Git for Mission/Work, DB-inline for Tenant).Nothing.
TasksFacadeService + "Ever Works Task Tracker" plugin (per ADR-013)First-party plugin uses platform DB tables via the existing TaskRepository. Facade routes all task operations through the active task-tracker plugin (one per tenant). Third-party plugins (Linear / Jira / GH Issues) proxy CRUD to their respective APIs and store side-table task_agent_assignments for Agent assignees when the external tracker doesn't support that.ITaskTrackerPlugin contract; facade routing + side-table Agent assignment when supportsAgentAssignees=false.
TaskTransitionServiceState-machine guarded transitions — closest precedent is WorkProposalService.acceptInternal() with its fromStatuses check.The full state machine.
TaskChatServicePolling endpoint + mention parsing — analogous to AI Conversation's ConversationController.Mention parsing + dispatch hooks.
TaskNotificationServiceThin wrapper that calls existing NotificationsService.create() (notification.entity.ts shape).Recipient computation logic.

Genuinely new service files: ~14. All thin (mostly orchestration over existing services); the heaviest is PromptAssemblerService.


3. Background jobs — map to reuse

New Trigger.dev taskReuse
agent-heartbeat-dispatcherIdentical shape to mission-tick task (mission-tick.task.ts) — schedules.task({cron: '* * * * *'}) + service call.
agent-heartbeatIdentical shape to work-generation task — task() with maxDuration: 30 * 60, bootstraps Nest, calls service via remote proxy.
agent-task-executeSame as above with maxDuration: 60 * 60.
agent-chat-replySame with maxDuration: 5 * 60.

All four reuse the existing internal RPC channel (ADR-002): x-trigger-secret header + createRemoteProxy(). Genuinely new transport: 0.


4. Controllers / endpoints — map to reuse

Every new controller decorator pattern lives in existing code:

PatternExisting example
@Controller('api/agents') (unversioned)@Controller('api/me/missions') in missions.controller.ts
Cross-user 404 (not 403) on readAI Conversation controller
@CurrentUser() decoratorEvery authenticated controller
@Throttle({ default: { limit: N, ttl: M } })quickCreateWork in works.controller.ts
Offset pagination DTO with {data, meta}Every list endpoint
ParseUUIDPipe for path paramsEvery detail endpoint
class-validator DTOs in dto/ subfolderEvery module
SSE (only for streaming chat) text/event-streamopenai-compat.controller.ts
Activity emission via event listenerWorkCreatedEvent → ActivityLogListener.onWorkCreated

Genuinely new controller shape: 0. Even the SSE streaming for Agent chat reply (if we land N10-c) reuses the openai-compat pattern.


5. Web (frontend) — map to reuse

New UI surfaceReuse
/agents list (Cards/Table/Kanban)View-mode switcher + localStorage persistence: works-client.tsx. Kanban shape: WorksKanbanView.tsx.
/agents/[id] tab stripWorkTabs.tsx shape; new component AgentTabs.tsx copies it.
Agent detail Dashboard (live, charts)Stat cards: apps/web/src/components/dashboard/*Tile.tsx. Bar chart: same lib already in use.
Activity tab per AgentActivityFeedClient.tsx extended with agentId filter.
Instructions tab (5-file editor)KbEditor.tsx (Tiptap) wrapped in a 5-pill switcher. Zero new editor stack.
Budgets tabShape identical to existing Work Budgets / Mission Budget cards (per BudgetSummaryCard.tsx).
Create-Agent dialog (2-step)Form patterns from WorkManualForm.tsx: useState + useTransition + server actions.
/skills list (3 sections)PluginsList.tsx shape: search + filter + cards.
/skills/[id] Body + Bindings tabsNew mini-tabs; Tiptap editor for body.
/tasks listView-mode switcher from /works. New TasksKanbanView.tsx adapts WorksKanbanView.tsx columns.
Task detail pageStacked layout; right-rail metadata + main scroll. Chat: lightweight Tiptap.
Sidebar itemsInsert into DashboardSidebar.tsx config array.
New dashboard tilesNew AgentsCountTile.tsx, TasksInProgressTile.tsx — clone existing tile shape.

Genuinely new components: ~12, all thin. No new state lib, no new design system, no new routing.


6. Migration ordering

Run in this order (all forward-only, additive):

  1. CreateAgentsTables.ts — creates agents, agent_runs, agent_run_logs, agent_budgets, agent_memberships.
  2. AddAgentIdToPluginUsageEvents.ts — additive column.
  3. ExtendBudgetOwnerTypeEnum (no DB change; enum string union in TS code).
  4. CreateSkillsTables.ts — creates skills, skill_bindings.
  5. CreateTasksTables.ts — creates tasks, task_assignees, task_reviewers, task_approvers, task_blocks, task_relations, task_chat_messages, task_attachments.
  6. AddTaskAuxTables.tsuser_task_counter, task_watchers, task_kb_mentions.
  7. AddTaskIdToPluginUsageEvents.ts — additive column.

Each migration:

  • runs in a single transaction;
  • ships with a down() that drops the new tables/columns (safe rollback within minutes of deploy);
  • emits a startup log line "Applied migration NNN".

Rollback story: revert the API to the previous tagged build; the migrations' down() drops the new tables. No data loss for non-feature flows.


7. N+1 risk catalog

The dispatcher and list endpoints are the hot paths. Each one's risk + mitigation:

RiskMitigation
GET /agents with each card showing bound-skills-countAggregate query with LEFT JOIN skill_bindings GROUP BY agentId. Single round trip.
GET /agents with each card showing lastRunStatusStored denormalized on agents.lastRunStatus (already in entity).
GET /tasks Kanban with 500 items × assignees × labelsLoad tasks; then one batch query per join (assigneeIds IN (...)). Three queries total.
Heartbeat dispatcher with 10k active Agents per tickSingle SELECT with index (status, nextHeartbeatAt); CAS-claim atomic SQL UPDATE per agent.
AgentRunService.execute() loading files + skills + activityAll loads in Promise.all() (see agent-prompt-assembly.md §8).
GET /tasks/:id/chat with mentions referencing other tablesMentions stored as embedded JSON; UI dereferences via batched lookups on a separate ?expand=mentions query.
Skill.resolveActive() for Agent with bindings at 5 scope tiersOne JOIN query covering all scope tiers; results de-duped in app by slug.

Lazy loading (TypeORM lazy: true) is avoided for hot paths — explicit JOINs only.


8. Caching strategy

CacheBacked byInvalidation
Skill catalog (in-memory)SkillCatalogService boot-time load + MapAPI restart; no runtime invalidation needed
Agent file content (5 min LRU)Existing cache_entries table (TypeORM-backed)Explicit cache.delete(agentId) on PUT
Resolved active skills per Agentcache_entries 60s TTLExplicit delete on skill_binding mutation
Activity feedClient polling, no server cachen/a
Agent run dashboard chartsServer-side aggregation, 30s response cacheTTL only
Mission/Idea/Work tab countsSame as existing tab-count cachingn/a

We use the existing cache_entries distributed-cache table (cache.entity.ts) so multi-pod deployments share state. No Redis added.


9. Test infrastructure reuse

Test typeExisting patternReuse
Entity unitpackages/agent/src/entities/__tests__/work.entity.spec.tsSame Jest setup; same factory pattern.
Repository unitpackages/agent/src/database/repositories/__tests__/*.spec.tsMock DataSource pattern (existing).
Service unitpackages/agent/src/services/__tests__/*.spec.tsNestJS @nestjs/testing module setup.
API e2e (NestJS)apps/api/test/*.e2e-spec.tsExisting test app bootstrap with in-memory SQLite or test Postgres.
Dispatcher CAS raceExisting test for WorkScheduleDispatcherService.markRunDispatchedSpin up 2 parallel calls; assert exactly one wins.
Trigger.dev mockExisting pattern in packages/tasks/src/__tests__/Mock runs.trigger() returning a stub run-id.
AI provider mockExisting pattern: mock AiFacadeService.createChatCompletion to return canned responsesSame.
Web Playwright e2eapps/web/playwright.config.ts + apps/web/tests/Same.
Plugin contract testpackages/plugin/src/contracts/__tests__/Add a contract test for the reserved IExternalTaskTrackerPlugin interface.

E2E cost concern: tests that call real AI providers cost real $. Existing posture: AI calls always mocked in CI via the test-provider mock. We inherit this. Local dev that wants to hit a real provider uses .env.test.local with their own API key.


10. Local dev experience

ConcernSolution
Bootstrap an Agent without GitHub repoTenant-scope Agents work DB-only; no Git needed.
Force a heartbeat without waiting for cronPOST /agents/:id/run-now available in UI.
Force a Trigger.dev cron in pnpm dev:triggerExisting dev server runs cron at higher frequency for testability.
Mock AI provider for offline devOPENAI_MOCK=true env var (existing); responds with canned data.
Inspect prompt assembly outputNew POST /agents/:id/dry-run (see QUESTIONS N4) — returns the assembled prompt without calling AI.
Reset Agent stateDELETE /agents/:id cascades; or pnpm db:reset --feature=agents (script to add).

11. What's GENUINELY new (the minimal addition set)

This is the only code that has no clear precedent in the existing platform and therefore needs the most design care:

  1. PromptAssemblerService — the 11-segment system-message assembly. ~250-400 lines of code. Risk: prompt-engineering correctness; mitigated by progressive disclosure + token budgeting + extensive unit tests.
  2. AgentToolService.resolveAllowedTools — the per-Agent tool catalog with permission gates. ~150 lines. Risk: privilege escalation; mitigated by security model §7.
  3. SkillBindingRepository.resolveActive — priority-sorted resolver across 5 scope tiers. ~80 lines. Risk: incorrect resolution order; mitigated by exhaustive unit tests.
  4. TaskTransitionService — state machine with blocker/approver guards. ~120 lines.
  5. Cycle detection (parent + blockers) — recursive CTE or iterative walker. ~60 lines.
  6. Mention parser — extracts @<slug> / [[kb-slug]] from rich-text body. ~80 lines.
  7. Two custom Trigger.dev tasks (agent-heartbeat, agent-task-execute) — bodies are thin orchestrators.
  8. Web UI: ~12 new React components per the table in §5. Each is a thin variant of an existing component.

Conservative estimate: ~3000 lines of new TS code total, of which ~600 is genuinely novel. Spread across packages/agent/, apps/api/, apps/web/, packages/tasks/. Add ~1500 lines of test code.

For a comparable feature size, this slot is ~⅓ the size of the AI Conversation feature when it landed.


12. Deployment / rollout impact

ConcernImpact
Trigger.dev run volumeNew dispatcher fires every minute. If 100 active Agents and dispatcher batches in 1 task → +~525k runs/month. Cost the Trigger.dev tier before launch.
DB row volumeagent_runs at 1-min heartbeat × 100 Agents = 144k rows/day. With 30-day rolling history, ~4M rows. Index on (agentId, startedAt) handles.
API memorySkill catalog cache: ~10 MB (1000 entries × ~10 KB). Agent file cache: ~100 KB per active Agent × cap = bounded.
API CPUHeartbeat AI calls happen on the worker, not the API. API CPU impact: negligible.
Worker memoryEach agent-heartbeat run bootstraps Nest (~100 MB) + holds prompt context (~50 KB). Trigger.dev's per-run isolation handles this.
Plugin usage events tableAlready at heavy write volume; +1 column (agentId) + 1 column (taskId). Index (agentId, occurredAt) adds ~10% storage.
Network egress to AI providersWhatever the user authorizes; bounded by per-Agent budgets.

No new infrastructure deployed (Redis, k8s, queue system, etc.).



14. PR / shipping plan

Estimated ~12-18 PRs for full feature delivery:

PRScopeRisk
1agents table + migration + entity + repository unit testsLow
2agent_runs / agent_run_logs / agent_budgets + migrationLow
3AgentService + AgentsController (read-only)Low
4AgentFileService + PUT/GET files endpointsMedium
5/agents web list + Instructions tab + Create dialogMedium
6agent-heartbeat-dispatcher Trigger.dev task + CAS-claimMedium
7agent-heartbeat task + AgentRunService.execute() + PromptAssemblerServiceHigh
8Skill catalog + skills + skill_bindings + read-only APILow
9Skill mutations + /skills page + Bindings tabLow
10Skill injection into AI calls + getSkillBody toolMedium
11tasks family of tables + migrations + repositoriesLow
12TaskService + TasksController + /tasks list + CardsMedium
13Task detail page + chat backendMedium
14Task Kanban + per-target tabs (incl. Mission tab strip)Medium
15agent-task-execute + agent-chat-reply Trigger.dev tasksMedium
16Tools surface (createTask, etc.) wired to Agent runsHigh
17Dashboard tiles + Recent Tasks block + notifications wiringLow
18Default-on rollout + docs site updatesLow

Critical-path PRs: 7 (AgentRunService + PromptAssembler) and 16 (tools wired to runs). Everything else can land in parallel after #3.


15. Hand-off checklist

Before any implementation PR opens:

  • All open QUESTIONS answered (or punted with a documented [NEEDS CLARIFICATION] mark).
  • Migrations reviewed for synchronize: true accidents.
  • Constitution gates ticked in every feature spec.
  • Test plan reviewed (unit + e2e + Playwright).
  • Operator approved the default budget caps.
  • Trigger.dev project tier confirmed (cost of +500k runs/month).
  • Sentry tags added to telemetry config.

16. References