AW-09 — Runs and receipts · Task breakdown
Ordered, executable tasks derived from plan.md. Each carries explicit file paths, a definition of done, and its phase. Work top to bottom; nothing below requires a guess.
Epic ID: AW-09-runs-receipts
Spec: ./spec.md · Plan: ./plan.md
Status: Draft
Last updated: 2026-09-06
How to use
- Tasks are sequential unless marked
(parallel). - Phase boundaries are release boundaries: P1, P2 and P3 each leave
developgreen and shippable on their own. MODIFYmeans an existing file changes;CREATEmeans a new file.- Every task that changes an entity ships its migration in the same PR (Constitution V).
- Add new tasks at the bottom rather than renumbering.
Phase 1 — The ledger
No migration. Reads only what agent_runs and agent_run_logs already hold.
P1.A — Contracts
-
T1. Add the run-ledger contract types.
- CREATE
packages/contracts/src/runs/run-ledger.types.tswithRunLedgerGranularity,RunLedgerWindow,RunLedgerRow,RunLedgerPage,RunWindowStats,RunTokenSplit,RunCalendarDay,RunCostBreakdown,RunReceipt,UpcomingFireexactly as shaped in plan.md §3.3. - CREATE
packages/contracts/src/runs/index.tsre-exporting the above. - MODIFY
packages/contracts/src/index.ts— addexport * from './runs/index.js';beside the existing./agents/index.jsline. - Import
AgentRunTimelineEntryfrom the existing agents contract; do not redeclare it. - Done when:
pnpm --filter @ever-works/contracts buildemits declarations with no DTS error, and no type in the new file duplicates one that already exists in the package.
- CREATE
-
T2 (parallel with T1). Add the failure-code and skill-use types to the Run entity module without any column change yet.
- MODIFY
packages/agent/src/entities/agent-run.entity.ts— exportRunFailureCodeandRunSkillUsetypes only (columns land in P2/P3), and add'email'to theAgentRunTriggerKindunion with the comment that no row carries it until AW-05 writes one. - Done when:
pnpm --filter @ever-works/agent type-checkis green and no migration is generated (the union widens avarchar(16)column; nothing in the schema changes).
- MODIFY
P1.B — Read model (agent package)
-
T3. Window resolution helper. - CREATE
packages/agent/src/agents/run-window.ts—resolveWindow({ granularity, anchorDate, timezone })returningRunLedgerWindow; Monday week start; month = calendar month in the given timezone; clamp tonow − 12 months…now + 7 daysand setclamped. - CREATEpackages/agent/src/agents/__tests__/run-window.spec.ts— Day/Week/Month boundaries, a DST transition in a non-UTC zone, the clamp at both ends, an invalid timezone falling back to UTC. - Done when: the spec passes andresolveWindowhas no dependency on TypeORM or Nest. -
T4. Ledger repository reads.
- MODIFY
packages/agent/src/database/repositories/agent-run.repository.ts— addlistLedgerPage(userId, scope, window, filters, limit, cursor),countLedger(userId, scope, window, filters)andcalendarCounts(userId, scope, month, timezone, filters). Every query filtersuserId = :userIdand the activeorganizationIdinside the repository, exactly likelistSessionsForUserdoes today. Cursor is the existing<epochMillis>_<uuid>shape. - CREATE
packages/agent/src/database/repositories/agent-run.ledger.spec.ts— a second user's rows are unreachable through every filter permutation; cursor paging is stable when rows are inserted between pages. - Done when: both specs pass and no new raw cross-table SQL was introduced.
- MODIFY
-
T5.
RunLedgerService.- CREATE
packages/agent/src/agents/run-ledger.service.ts—listRuns,getStats,getCalendar. Resolves the window (T3), calls the repository (T4), maps rows toRunLedgerRow(resolving agent name + archived flag and mission title in one batched lookup each, never per row), derivesscheduleKey(agent_heartbeat:<agentId>fortriggerKind === 'heartbeat';nullotherwise in P1). - CREATE
packages/agent/src/agents/run-ledger.service.spec.ts— filter composition (AND across dimensions, OR within),scheduleKeyderivation, batched name resolution (assert at most one lookup call per entity kind for a 50-row page). - Done when: specs pass and a 50-row page issues a bounded, constant number of queries.
- CREATE
-
T6. Window statistics.
- CREATE
packages/agent/src/agents/run-window-stats.ts— pure functions computingsuccessRate(completed ÷ terminal, one decimal,nullwhen terminal = 0),errorCount,totalDurationMs,costCents,creditsDebited,tokens,byStatus,byTrigger, andrepeatFailures(group failed rows byscheduleKey, keep groups with ≥ 2). - CREATE
packages/agent/src/agents/__tests__/run-window-stats.spec.ts— zero terminal runs, exactly 1 / 2 / 3 failures per schedule,nullvalues mixed into token sums. - Done when: specs pass; no function reads a repository (all inputs are rows).
- CREATE
-
T7.
RunReceiptService(P1 shape).- CREATE
packages/agent/src/agents/run-receipt.service.ts— loads one run scoped to the caller, assemblesRunReceipt:row,counts,filesTouched,related(mission, task, work, agent, schedule link),timeline(delegating to the existingAgentRunLogRepository.findTimelineByRun/countByRunStepsand settingcaptureTruncatedfrom thecapture-truncatedmarker),skills: [],failure: null, and acostblock carrying only the settledtotalCents/creditsDebitedwithdetailRetained: false. - CREATE
packages/agent/src/agents/run-receipt.service.spec.ts— a foreign run id resolves tonull(never a partial object);captureTruncateddetection; a run with no mission returnsmissionId: nullrather than omitting the key. - Done when: specs pass and the returned shape matches
RunReceiptin the contract exactly, including the fields P2 will fill.
- CREATE
-
T8. Wire the services into the agent module.
- MODIFY
packages/agent/src/agents/agents.module.ts— provide and exportRunLedgerServiceandRunReceiptService. - MODIFY
packages/agent/src/agents/index.ts— export both plusresolveWindowand the stats helpers. - Done when:
apps/api/src/agents/agents.module.spec.tsstill passes and the new providers resolve in a Nest testing module.
- MODIFY
P1.C — API
-
T9. Runs module and DTOs.
- CREATE
apps/api/src/runs/runs.module.ts— imports the agentAgentsModule,SchedulesModuleandSubscriptionsModule. - CREATE
apps/api/src/runs/dto/run-ledger.dto.ts—ListRunsQueryDto,RunStatsQueryDto,RunCalendarQueryDto,RunReceiptQueryDtowith the exact validators and bounds in plan.md §4.1. Every numeric query field carries@Type(() => Number)before@IsInt(). - MODIFY
apps/api/src/api.module.ts— registerRunsModule. - Done when:
pnpm --filter ever-works-api type-checkis green and the module boots in a Nest testing module.
- CREATE
-
T10. Runs controller.
- CREATE
apps/api/src/runs/runs.controller.ts—@Controller('api/runs'),@UseGuards(AuthSessionGuard),@ApiTags('Runs'),@ApiBearerAuth('JWT-auth'). Endpoints in this declaration order:GET stats,GET calendar,GET /(root),GET :runId/receipt. Literal segments must precede:runId. Throttles per plan.md §4. Swagger@ApiOperation/@ApiResponseon every route. No route accepts a user, tenant or Organization id. - Done when:
GET /api/runs/statsnever reachesParseUUIDPipe, and a missing or foreignrunIdboth return an identical404body.
- CREATE
-
T11. Controller specs.
- CREATE
apps/api/src/runs/runs.controller.spec.ts— route ordering; DTO rejection of a 120-day window,limit=500,qof 1 character, an unknown timezone;limitdefaults to 50; no parameter can widen scope. - CREATE
apps/api/src/runs/runs.controller.receipt.spec.ts— identical404for unknown and foreign ids; timeline paging via cursor; the P1 cost-block shape. - Done when: both specs pass under
cd apps/api && pnpm test.
- CREATE
P1.D — Web
-
T12. Routes, constants and navigation.
- MODIFY
apps/web/src/lib/constants.ts— addDASHBOARD_RUNS: '/runs'andDASHBOARD_RUN: (runId: string) => '/runs/' + runId. - MODIFY
apps/web/src/components/dashboard/DashboardSidebar.tsx— add the Runs entry immediately above the existing Activity entry. - Done when: the sidebar renders Runs and its active state highlights on
/runsand/runs/<id>.
- MODIFY
-
T13. Typed client, server actions and the BFF proxy.
- CREATE
apps/web/src/lib/api/runs.shared.ts— client-safe mirrors of the contract types plusrunRowCursor(),formatTokenSplit(),nextTimeLimitStep()(pure). - CREATE
apps/web/src/lib/api/runs.ts—server-only;runsAPI.list/stats/calendar/receiptviaserverFetch, forwardingX-Scope-Slug. Mirrorapps/web/src/lib/api/activity-log.ts. - CREATE
apps/web/src/app/actions/runs.ts—getRuns,getRunStats,getRunCalendar,getRunReceipt; auth-guarded likeapps/web/src/app/actions/activity-log.ts. - CREATE
apps/web/src/app/api/runs/[section]/route.ts— cookie→Bearer proxy with a closed section allowlist (list,stats,calendar,receipt;upcomingadded in P3) and an explicit query-param allowlist — never the raw query string. Mirrorapps/web/src/app/api/usage/costs/[section]/route.ts. - CREATE
apps/web/src/lib/api/runs.shared.unit.spec.ts— the pure helpers, includingformatTokenSplitreturning the not-measured marker fornulland never0. - Done when: an unknown section 404s in the proxy before any upstream call, and an unlisted query param is dropped rather than forwarded.
- CREATE
-
T14. Extract the timeline renderer so the receipt can reuse it.
- CREATE
apps/web/src/components/agents/RunTimeline.tsx— the timeline list, tool rows, previews, truncation markers and "Load older entries" control, lifted verbatim fromSessionDetailClient.tsx. - MODIFY
apps/web/src/components/agents/SessionDetailClient.tsx— import and renderRunTimelinein place of the inlined markup. No behaviour change. - Done when:
apps/web/src/components/agents/SessionDetailClient.unit.spec.tsxpasses unchanged — that spec is the proof the extraction was behaviour-neutral.
- CREATE
-
T15. The page shells.
- CREATE
apps/web/src/app/[locale]/(dashboard)/runs/page.tsx— RSC. ParsessearchParams(g,d,agent,kind,status,mission,work,model,q,run), resolves the caller's timezone, fetches list + stats + calendar withPromise.allSettled, and passes each result with its own error flag.generateMetadatausesmetadata.pages.runs. - CREATE
apps/web/src/app/[locale]/(dashboard)/runs/[runId]/page.tsx— standalone receipt;notFound()when the receipt read 404s. - Done when: a forced failure of any one of the three fetches still renders the page.
- CREATE
-
T16. Ledger components.
- CREATE, under
apps/web/src/components/runs/:RunsClient.tsx,RunsCalendarBar.tsx,RunsMiniCalendar.tsx,RunsFilters.tsx,RunsTable.tsx,RunRow.tsx,RunsRail.tsx,RunsEmptyState.tsx,RunsShortcutSheet.tsx,index.ts. RunsClientowns granularity (persisted inlocalStorageunderruns-granularity), the anchor date, filters, focused row and open-receipt id, and mirrors all of them into the URL viarouter.replace.- Polling: start only when the window includes now and a listed run is
queued/running; interval 5 s; stop ondocument.hidden; merge by id preserving scroll, focus and the open receipt. RunsTablerenders a<table>with a caption naming the window and the active filters; outcome is an icon plus a text label.- Done when: the ledger renders for Day, Week and Month; the rail is a filter shortcut; the three empty variants and the load-error variant all render as specified in spec.md §6.
- CREATE, under
-
T17. Keyboard layer.
- MODIFY
apps/web/src/components/runs/RunsClient.tsx— a single document-level handler implementing←,→,t,d,w,m,j,k,Enter,o,Esc,/,f,?, inert whenever aninput,textarea,selectorcontenteditablehas focus. - CREATE
apps/web/src/components/runs/RunsCalendarBar.unit.spec.tsxandapps/web/src/components/runs/RunsMiniCalendar.unit.spec.tsx— stepping, today, the two mini-calendar marker shapes, the 12-month reach notice. - Done when: typing
dinside the search box does not switch the view.
- MODIFY
-
T18. Receipt panel (P1 blocks).
- CREATE
apps/web/src/components/runs/RunReceiptPanel.tsx— focus-trapped dialog; blocks in the FR-25 order; rendersRunTimeline(T14); "so far" labelling for non-terminal runs;Esccloses and returns focus to the originating row. - CREATE
apps/web/src/components/runs/RunReceiptPanel.unit.spec.tsx. - Done when: opening from a row and opening
/runs/<id>directly render the same blocks.
- CREATE
-
T19. i18n keys.
- MODIFY
apps/web/messages/en.json— add every key listed in plan.md §8 except thetimeLimit.*,failure.*,upcoming.*,repeatFailure.*andexport.*groups (those land with their phases). Confirm every leaf name is camelCase and contains no literal.. - MODIFY the 20 sibling locale files in
apps/web/messages/— same keys; English values are acceptable until translation lands. A missing key is a runtime failure; an untranslated value is not. - Done when:
pnpm --filter web test(the i18n key-consistency check) is green for all 21 files.
- MODIFY
-
T20. Cross-links into Runs (additive; nothing is removed).
- MODIFY
apps/web/src/components/agents/AgentSessionsClient.tsx— header link "Open in Runs" →/runs?agent=<id>. - MODIFY
apps/web/src/components/agents/SessionDetailClient.tsx— "Open receipt" →/runs/<runId>. - MODIFY
apps/web/src/components/agents/AgentActivityClient.tsx— "See this agent in Runs". - MODIFY
apps/web/src/components/settings/costs/CostsSettings.tsx— link each Top runs row to/runs/<runId>; keepCostsSettings.unit.spec.tsxgreen. - Done when: every existing spec for those four components still passes.
- MODIFY
P1.E — End-to-end
-
T21. Playwright coverage for the ledger.
- CREATE
apps/web/e2e/runs-ledger.spec.ts,apps/web/e2e/runs-filters.spec.ts,apps/web/e2e/runs-receipt.spec.ts,apps/web/e2e/runs-empty-and-errors.spec.ts,apps/web/e2e/runs-accessibility.spec.ts— scenarios per plan.md §10.4. - MODIFY
apps/web/e2e/COVERAGE.md— record the new specs and the shard they belong to. - Query by role and accessible name, not by test id.
- Done when: all five pass locally and in CI, and the axe pass reports no violations on the ledger or an open receipt.
- CREATE
-
T22. P1 gate.
- Run
pnpm format && pnpm lint && pnpm type-check && pnpm test && pnpm buildfrom the repo root and confirm green. - MODIFY
docs/specs/features/agent-workspace/TRACKER.md— set AW-09 specDraft, implIn progress, note "P1 merged". - Done when: the acceptance groups Ledger, Navigation, Filters and rail and the non-cost receipt criteria in spec.md §8 all pass.
- Run
Phase 2 — The cost breakdown
Migration A. Makes the token split, the Skills a run loaded, and per-model cost durable.
-
T23. Extend the token tracker with cache figures.
- MODIFY
packages/plugin/src/ai/token-usage.tracker.ts— widenTokenUsagewith optionalcacheReadTokensandcacheWriteTokens; read them from the same tolerant field set the tracker already uses (cache_read_input_tokens,cache_creation_input_tokens,cached_tokens,input_token_details.cache_read, plus camelCase twins). Leave bothundefinedwhen nothing is reported — never coerce to0. - MODIFY
packages/plugin/src/ai/__tests__/token-usage.tracker.spec.ts— one case per tolerated field name, plus "absent stays undefined". - Done when: no provider name appears anywhere in the file (Principle II) and the spec
passes under
cd packages/plugin && pnpm test.
- MODIFY
-
T24. Thread the split through the AI path.
- MODIFY
packages/plugin/src/ai/ai-operations.ts—mapTokenUsagepasses the two optional fields through. - MODIFY
packages/agent/src/agents/agent-ai-dispatch-facade.ts— widen theusageshape with the same two optional fields. - MODIFY
packages/agent/src/facades/ai.facade.ts— passinputTokens/outputTokens/cacheReadTokens/cacheWriteTokensintopluginUsageService.record(...). Keep writingmetadata.promptTokens/metadata.completionTokensfor one release cycle (Principle X). - MODIFY
packages/agent/src/usage/plugin-usage.service.ts— accept and forward the four optional fields. - MODIFY
packages/agent/src/database/repositories/plugin-usage.repository.ts— persist them. - Done when:
packages/agent/src/usage/plugin-usage.service.spec.tscovers a call with and without cache figures and assertsnull(not0) in the absent case.
- MODIFY
-
T25. Entity columns + Migration A (same PR).
- MODIFY
packages/agent/src/entities/agent-run.entity.ts— addinputTokens,outputTokens,cacheReadTokens,cacheWriteTokens,modelIds,primaryModelId,skillsUsed,toolCallCount,creditsDebitedexactly as typed in plan.md §3.1. - MODIFY
packages/agent/src/entities/plugin-usage-event.entity.ts— addinputTokens,outputTokens,cacheReadTokens,cacheWriteTokens. - CREATE
apps/api/src/migrations/1791090000000-AddRunReceiptTelemetry.ts— additiveALTER TABLEfor both tables plusCREATE INDEX idx_agent_runs_user_started ON agent_runs (userId, startedAt). Generate withcd apps/api && pnpm typeorm migration:generate -d typeorm.config.ts src/migrations/AddRunReceiptTelemetry, then rename to the timestamp above. - Done when: the generated SQL contains no
DROP COLUMN, noNOT NULLwithout a default and no type narrowing;down()drops only whatup()added; a fresh boot withRUN_MIGRATIONS=trueapplies it cleanly and a second boot is a no-op.
- MODIFY
-
T26. Per-run rollups in the execution loop.
- MODIFY
packages/agent/src/agents/agent-run.service.ts:- extend the existing per-round
addRunTokensfold to also accumulateinputTokens,outputTokens,cacheReadTokens,cacheWriteTokens, appendround.modeltomodelIds, and incrementtoolCallCount; - after
selectSkillsWithinBudget(...), writeskillsUsedasRunSkillUse[]—loadedfor the selected set,droppedwithreason: 'skill-budget',suppressedwithreason: 'tool-grant'. Keep the existingWARNlog rows.
- extend the existing per-round
- CREATE
packages/agent/src/agents/__tests__/run-skill-capture.spec.tsand extendpackages/agent/src/database/repositories/agent-run.telemetry.spec.ts. - Done when: a run that drops a Skill records it with the right reason, and token counters
are written incrementally (visible while the run is still
running).
- MODIFY
-
T27. Settlement stamps.
- MODIFY
packages/agent/src/subscriptions/credits/run-cost-settlement.service.ts— stampprimaryModelId(highest token count among the run's usage rows) andcreditsDebited(the amount of theCONSUMPTIONrow it writes) in the same terminal-transition write. - MODIFY
packages/agent/src/subscriptions/credits/run-cost-settlement.service.spec.ts— a retried settlement under the samerun:{runId}idempotency key does not double-count. - Done when: the spec passes and no new write path to
credit_ledger_entrieswas added.
- MODIFY
-
T28. Receipt cost and skills.
- MODIFY
packages/agent/src/agents/run-receipt.service.ts— populateRunCostBreakdown: totals,tokens,byModel,byCapability,creditsDebited,byoKeyOnly, anddetailRetained(false once the run'soccurredAtis older than 12 months, matching theplugin_usage_eventsprune window); populateskillsfromagent_runs.skillsUsed. - MODIFY
packages/agent/src/agents/run-receipt.service.spec.ts— "not attributable" vs0; BYO-key-only detection; the retention flip; a pre-migration run rendering all-nulltokens. - Done when: no branch can produce a fabricated number; absent data is
null, never0.
- MODIFY
-
T29. Rail token and spend figures.
- MODIFY
packages/agent/src/agents/run-window-stats.ts— sum the four split counters intoRunTokenSplit, treatingnullas "not measured" rather than zero when every row in the window isnull(in which case the total isnull). - MODIFY
packages/agent/src/agents/__tests__/run-window-stats.spec.ts. - Done when: a window entirely composed of pre-migration runs shows "—", not
0.
- MODIFY
-
T30. CSV export.
- MODIFY
apps/api/src/runs/runs.controller.ts— addGET exportbefore the:runIdroute;@Throttle({ long: { limit: 10, ttl: 60_000 } }). - MODIFY
apps/api/src/runs/dto/run-ledger.dto.ts—RunsExportQueryDtoextending the stats DTO withformat. - MODIFY
packages/agent/src/agents/run-ledger.service.ts—countLedgerguard first, then a batched (1,000-row) async iterator; refuse over 50,000 rows or 92 days with the stable coderuns.exportTooLargebefore any streaming begins. - CREATE
apps/web/src/app/api/runs/export/route.ts— pipesresponse.bodythrough; never buffers. Mirrorapps/web/src/app/api/credits/usage/export/route.ts. - CREATE
apps/api/src/runs/runs.controller.export.spec.ts— refusal at 50,001 rows and at 93 days before any query; headers; streamed body. - Done when: a refused export produces no database read beyond the count.
- MODIFY
-
T31. Cost and skills UI.
- CREATE
apps/web/src/components/runs/RunReceiptCost.tsxandapps/web/src/components/runs/RunReceiptSkills.tsx. - CREATE
RunReceiptCost.unit.spec.tsx— "Not reported by this provider" fornullcache, the{percent} % of inputline, the retention notice, "so far" labelling, the "Your own provider key" case. - MODIFY
apps/web/src/components/runs/RunReceiptPanel.tsx— mount both blocks. - MODIFY
apps/web/src/components/runs/RunsRail.tsx— show spend and tokens. - MODIFY
apps/web/src/components/runs/RunsFilters.tsx— add the model filter, populated from the window's observed models. - MODIFY
apps/web/messages/en.json+ the 20 sibling locales — add thereceipt.*cost and skills keys and theexport.*group. - Done when: no cost surface can render a
0where the underlying value isnull.
- CREATE
-
T32. P2 end-to-end.
- MODIFY
apps/web/e2e/runs-receipt.spec.ts— assert the token split, the per-model breakdown, a loaded/dropped Skill pair, and the not-reported cache copy. - CREATE
apps/web/e2e/runs-export.spec.ts— a successful export downloads a CSV whose header row matches the plan's column list; an over-limit export shows the refusal dialog and starts no download. - MODIFY
apps/web/e2e/COVERAGE.md. - Done when: both pass in CI.
- MODIFY
-
T33. P2 gate.
- Root
pnpm format && pnpm lint && pnpm type-check && pnpm test && pnpm buildgreen. - MODIFY
docs/specs/features/agent-workspace/TRACKER.md— note "P2 merged". - Done when: the cost, token-split and Skills acceptance criteria in spec.md §8 all pass.
- Root
Phase 3 — Upcoming and remediation
Migration B. Adds failure classification, the per-Agent time limit, the Upcoming panel and the repeat-failure banner.
-
T34. Entity columns + Migration B (same PR).
- MODIFY
packages/agent/src/entities/agent-run.entity.ts— addfailureCodeandeffectiveTimeoutSeconds. - MODIFY
packages/agent/src/entities/agent.entity.ts— addmaxRunDurationSeconds(nullable;null= inherit the deployment default). - CREATE
apps/api/src/migrations/1791090100000-AddAgentRunFailureAndTimeout.ts. - Done when: additive-only SQL,
down()drops only the three new columns, and boot applies it cleanly twice.
- MODIFY
-
T35. Failure classifier. - CREATE
packages/agent/src/agents/run-failure-classifier.ts— pureclassifyFailure({ errorMessage, errorName, elapsedMs, effectiveTimeoutSeconds, cancelledBy, guardrailRefused, budgetStopped, creditsExhausted })→RunFailureCode. - CREATEpackages/agent/src/agents/__tests__/run-failure-classifier.spec.ts— every branch;timeoutonly when elapsed ≥ the effective limit;unknownas the honest fallback. - Done when: the classifier has no repository or Nest dependency. -
T36. Resolve and stamp the effective time limit.
- MODIFY
packages/agent/src/config/index.ts— addagents.resolveRunDurationSeconds(agentOverride?: number | null)returningagentOverride ?? getMaxRunDurationSeconds(), clamped to 60 … 14400. - MODIFY
packages/tasks/src/tasks/trigger/agent-heartbeat.task.tsandpackages/tasks/src/tasks/trigger/agent-task-execute.task.ts— resolvemaxDurationfrom the Agent's override, and stampagent_runs.effectiveTimeoutSecondsat dispatch. Classify inonFailureand writefailureCode. - Done when: neither task imports a vendor SDK at a new call site and both keep their
existing
schedules.task()/task()registration untouched (Constitution IV).
- MODIFY
-
T37. Classify swept runs.
- MODIFY
packages/agent/src/agents/agent-run-sweeper.service.ts— when reaping a stale run, stampfailureCodeas'timeout'when elapsed exceededeffectiveTimeoutSeconds, else'swept-stale', in the same CAS update it already performs. - MODIFY
packages/agent/src/agents/__tests__/agent-run-sweeper.service.spec.ts. - Done when: the sweeper writes one extra column and issues no extra query.
- MODIFY
-
T38. The write path for raising a limit.
- MODIFY
apps/api/src/agents/dto/agent.dto.ts—UpdateAgentDto.maxRunDurationSeconds?with@IsOptional() @Type(() => Number) @IsInt() @Min(60) @Max(14400);nullclears the override. - MODIFY
packages/agent/src/agents/agents.service.ts— persist it and include the field name in the existingagent_updatedactivity-log details. - CREATE
apps/api/src/agents/agents.controller.timeout.spec.ts— accepts 60 and 14400, rejects 59 and 14401, acceptsnull, 404s cross-user. - Done when: no new endpoint, guard or ownership check was introduced.
- MODIFY
-
T39. Upcoming fires endpoint.
- MODIFY
apps/api/src/runs/runs.module.ts— import the existingSchedulesModule. - MODIFY
apps/api/src/runs/runs.controller.ts—GET upcomingdeclared before:runId;UpcomingFiresQueryDto(horizonDays1…7 default 7,limit1…50 default 20). CallsSchedulesService.getSchedules(userId, scope, { enabledOnly: true }), drops null or out-of-horizonnextRunAt, sorts ascending, slices, maps toUpcomingFire, and returnsgeneratedAtso the client computes countdowns against server time. - CREATE
apps/api/src/runs/runs.controller.upcoming.spec.ts— clamps; paused/disabled/ended excluded; ascending order;generatedAtpresent. - Done when: no schedule query is reimplemented — the existing service is the only producer.
- MODIFY
-
T40. Failure, remediation and upcoming UI.
- CREATE
apps/web/src/components/runs/RunReceiptFailure.tsx,apps/web/src/components/runs/RaiseTimeLimitDialog.tsx,apps/web/src/components/runs/UpcomingFiresPanel.tsx,apps/web/src/components/runs/RepeatFailureBanner.tsx. RaiseTimeLimitDialogre-reads the Agent's current limit before proposing, usesnextTimeLimitStep()over the ladder 1800 → 3600 → 7200 → 14400, handles the already-raised and at-ceiling cases, and disables with the permission tooltip when the viewer cannot edit the Agent.UpcomingFiresPanelticks one shared 1 s interval, pauses ondocument.hidden, refetches every 60 s and on focus.RepeatFailureBannerdismissal is held insessionStorage.- MODIFY
apps/web/src/app/actions/runs.ts— addgetUpcomingFiresandraiseAgentTimeLimit. - MODIFY
apps/web/src/app/api/runs/[section]/route.ts— addupcomingto the allowlist. - CREATE
RunReceiptFailure.unit.spec.tsx,RaiseTimeLimitDialog.unit.spec.tsx,UpcomingFiresPanel.unit.spec.tsx. - MODIFY
apps/web/messages/en.json+ the 20 sibling locales — add thefailure.*,timeLimit.*,upcoming.*andrepeatFailure.*groups. - Done when: the shortcut appears only for
failureCode === 'timeout', and every state in spec.md §6.9–6.13 renders with the exact copy.
- CREATE
-
T41. P3 end-to-end.
- CREATE
apps/web/e2e/runs-failure-remediation.spec.tsandapps/web/e2e/runs-upcoming.spec.tsper plan.md §10.4. - MODIFY
apps/web/e2e/COVERAGE.md. - Done when: both pass in CI, including the "no change made" and at-ceiling branches.
- CREATE
-
T42. Analytics.
- MODIFY
apps/web/src/components/runs/RunsClient.tsxand the receipt components — emitruns_window_changed,runs_filter_applied,runs_receipt_opened,runs_time_limit_raised,runs_repeat_failure_banner_shown/_followed,runs_export_requested/_refusedthrough the existing PostHog binding (packages/monitoring/src/posthog/). - Done when: no event payload carries a summary, an error message, a tool payload or any other free-text user content — ids and enums only.
- MODIFY
-
T43. Documentation.
- CREATE
docs/features/runs.md— the user-facing page: what a Run is, how to read a receipt, what the token split means, the weekly review habit, and the time-limit ladder. - MODIFY
apps/docs/sidebarsPlatform.ts— add the page (the sidebar is manual; an unlisted file renders only as an orphan). - Done when:
pnpm --filter ever-works-docs buildproduces no broken-link warnings.
- CREATE
-
T44. P3 gate and epic close-out.
- Root
pnpm format && pnpm lint && pnpm type-check && pnpm test && pnpm buildgreen. - MODIFY
docs/specs/features/agent-workspace/TRACKER.md— AW-09 impl →Merged, thenVerifiedonce the e2e shard is green ondevelop. - MODIFY
docs/specs/features/agent-workspace/AW-09-runs-receipts/spec.md— status →Implemented;plan.mdandtasks.md→Done. - Done when: every checkbox in spec.md §8 is ticked and every open question in §9 is either answered in the spec or moved to a follow-up issue.
- Root
Definition of done for the epic
- All 44 tasks ticked.
- Both migrations applied cleanly on a fresh database and idempotent on re-boot.
pnpm format:check,pnpm lint,pnpm type-check,pnpm testandpnpm buildgreen at the repo root.- All seven-plus Playwright specs green and recorded in
apps/web/e2e/COVERAGE.md. - All 21 locale files carry every new key; no leaf key name contains a literal
.. - Every constitution gate in plan.md §12 re-confirmed against the merged code.
- Nothing that existed before this epic was removed, renamed or redirected.