Repository navigation
feat: ATIF post-processor worker with event-driven dispatch, portal UI, and resilience - #948
Cedric Vidal (cedricvidal) wants to merge 43 commits into
Conversation
Implement automatic ATIF (Agent Trajectory Interchange Format) file generation after coding runs complete. The system uses a new extensible post-processor worker with a handler registry pattern. Key components: - Post-processor worker (apps/workers/post-processor/) with AtifHandler that converts HAR captures to ATIF v1.7 JSON using the atifact package - PostProcessorDispatcher in the scheduler with atomic claim pattern to detect completed runs needing processing (real-time + backfill) - Version tracking via K8s Job writing to MongoDB services collection - API download endpoints: GET /requests/:id/atif and /runs/:runId/atif - ATIF included in archive exports - K8s deployment + KEDA ScaledObject + FluxCD version registration Job Architecture: - Scheduler-driven dispatch (single path for real-time and backfill) - postProcessorVersion + postProcessorStatus fields on RunState - atifUrl field on ConversationTurn - MongoDB-only version storage (no Redis key needed) - Extensible: add new handlers, bump version, auto-backfill Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add postProcessorStatus and postProcessorVersion to portal RunState type - Show post-processing badge in RunDetail header (queued/processing/failed) - Add Post-Processing column to RunsList table with status badges - Badge is hidden when status is 'done' on RunDetail (no noise) - Column visible by default; users can hide via column picker Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Show the badge for all states including 'done' (green 'Post-processed') so users always see the post-processing status in the header area. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Previously the badge was hidden when postProcessorStatus was undefined, meaning completed runs that haven't been dispatched yet showed nothing. Now shows 'pending' for done runs with no status or queued status. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use shorter, cleaner terminology throughout the UI: - RunDetail: 'Enrichment pending', 'Enriching…', 'Enriched', 'Enrichment failed' - RunsList column header: 'Enrichment' - RunsList cells: 'Pending', 'Enriching', 'Enriched', 'Failed' Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Shows a 'Download ATIF' button next to 'Download Archive' when the run has been enriched (postProcessorStatus === 'done') and turns have atifUrl. Also adds atifUrl to the portal ConversationTurn type and api helper. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds an 'ATIF' button next to 'Snapshot' and 'HAR' on each iteration card in the Turns tab, consistent with existing per-iteration downloads. Removes the separate header-level ATIF buttons in favor of this approach. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Shows enrichment status, version, and artifact count (ATIF trajectories) in the Details tab alongside Scenario, Persona, and Version Info cards. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds title attribute to enrichment badges showing 'Enrichment v1' (or whatever version) on hover, in both RunDetail header and RunsList table. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move the report trigger from coding workers (fire-and-forget on run completion) to the post-processor worker (after enrichment succeeds). This ensures reports can leverage enriched data like ATIF files. Changes: - Remove triggerReportGeneration() call from queue-processor.ts - Add triggerReportGeneration() to PostProcessor after status=done - Wire SCOPE_MT_API_URL config into post-processor - Add api dependency in docker-compose for post-processor Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
3 attempts with exponential backoff (1s, 2s, 4s). Client errors (4xx) are not retried since they indicate a permanent issue. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Documents withRetry function and @Retry decorator in docs/architecture/retry.md. Links from AGENTS.md doc table. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Provides a decision checklist, retry/no-retry table, configuration defaults, and anti-patterns. Triggered when writing or reviewing code that makes HTTP/REST calls or service-to-service interactions. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The portal sends null for systemPrompt, model, and timeoutMs when clearing these fields. The API handler already interprets null as 'remove field', but Zod rejected null at validation time. Added .nullable() to these optional fields. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Allows the reporting agent to fetch and analyze ATIF trajectory data for any iteration, enabling richer analysis of agent step-by-step behavior. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The tools were reading `response.turns` but the API returns turns nested under `response.run.turns`. This caused the report agent to always see 0 turns, producing useless reports. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Prevents future data-path bugs by annotating response.json() with the shared RequestDocument type. TypeScript now catches incorrect property access at compile time. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Covers typing response.json(), avoiding any, preferring optional chaining over fallback objects, and using shared types from packages/shared. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Creates compound index {run.postProcessorStatus: 1, updatedAt: -1} on
requests collection to optimize the PostProcessorDispatcher polling query.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Covers: indexing strategy (single-field for filters, 2-field compounds for ORDER BY), silent index drops, migration patterns, and RU cost awareness. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…/refactored-umbrella
Aligns with the shared-infra pattern used by all other services — mongodb container is not started when SCOPE_SHARED_INFRA=1 (using Azure CosmosDB). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The scheduler's PostProcessorDispatcher requires a version entry in the services collection. In K8s this is a separate Job, but in local dev (and with fresh databases like shared CosmosDB) the entry didn't exist, causing the dispatcher to skip all dispatch (targetVersion=0). Now the post-processor registers its version on startup before entering the poll loop, making it work reliably regardless of environment. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This reverts commit 128ec83f33f126de2bbca8f5fa35782e1a9f9c6a.
Follows the same pattern as the register-agents service and K8s Jobs. Runs the register-version.ts script before the post-processor starts, ensuring the scheduler can dispatch post-processing work. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The iteration query parameter is now required for both /atif routes. Returns 400 if missing, instead of silently defaulting to the last iteration with an ATIF file. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…points Deduplicate blob download logic shared across ATIF, HAR, tool-calls, and snapshots endpoints into a single downloadBlobToResponse() helper in blob-helpers.ts. Each endpoint retains its own URL-resolution logic (iteration parsing, field extraction, fallback behavior) but delegates the blob-fetch-and-pipe step to the shared helper. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Mirrors the pattern of har.test.ts, tool-calls.test.ts, and snapshots.test.ts with coverage for: missing iteration (400), invalid iteration (400), zero iteration (400), no ATIF available (404), successful proxy download, and blob-not-found (404). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ration Introduces artifact-route.ts with a generic registerArtifactRoutes() that registers both the request-level and per-run variants of an artifact download endpoint with shared findOne/resolveRun/RestError handling. Refactors atif, har, tool-calls, and snapshots to use it, reducing each file from ~90 to ~35 lines. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Renames the blob storage path, archive entry name, and download Content-Disposition filename from trajectory.json to atif.trajectory.json so the file purpose is immediately obvious. Existing blobs are unaffected — atifUrl in the DB is a full URL so old runs continue to serve correctly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Coder workers now enqueue post-processing directly when a run reaches terminal state, eliminating the 2s polling delay. The existing PostProcessorDispatcher polling loop remains as a backfill/catch-up mechanism for version upgrades and missed events. Changes: - Add postProcessorQueueName to QueueProcessorConfig - CodingAgentQueueProcessor creates a QueueClient for the post-processor queue and calls enqueuePostProcessing after finalWrite succeeds - Set run.postProcessorStatus="queued" atomically in finalWrite to prevent the polling dispatcher from double-enqueuing - Add onRunTerminal hook in BaseQueueProcessor for error-path dispatch - All three coder workers pass QUEUE_NAME_POST_PROCESSOR config - 4 new tests covering happy path, no-op, error tolerance, and stale-heartbeat redelivery dispatch Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… to 30 Now that coder workers dispatch post-processing event-driven on run completion, the polling loop only serves as backfill/catch-up. Reduce RU consumption by polling every 30s instead of 2s (separate env var SCHEDULER_PP_POLL_INTERVAL_MS). Increase batch size to 30 so backfills still make good progress per tick. Also documents SCHEDULER_POLL_INTERVAL_MS, SCHEDULER_PP_POLL_INTERVAL_MS, and QUEUE_NAME_POST_PROCESSOR in ENV_VARIABLES.md. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…/refactored-umbrella # Conflicts: # pnpm-lock.yaml
Covers ATIF generation flow, handler extensibility, version management, status lifecycle, configuration, and infrastructure details. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Fix unlink → rm(recursive) for temp directory cleanup in atif-handler - Add queue-send failure rollback in post-processor dispatcher - Fix artifact-route null check for resource.run - Escape quotes/backslashes in Content-Disposition filename - Fix migration 017 comment (reference migration 014, not 009) - Extract EnrichmentBadge component (dedup labels across portal) - Replace turns! non-null assertion with optional chaining - Add clarifying comments (container coupling, optimistic locking) - Update atif-handler test mock (unlink → rm) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Previously, polling stopped as soon as run.status reached 'done'. Post-processing happens after run completion, so the enrichment badge never updated without a manual page refresh. Now polling continues until postProcessorStatus is also terminal (done/failed). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Original author: @github-actions[bot] Test Results (Node.js 22)test: Run #1626
🎉 All tests passed! |
|
Original author: @github-actions[bot] Test Results (Node.js 22)test: Run #1627
🎉 All tests passed! |
|
Original author: @github-actions[bot] Test Results (Node.js 22)test: Run #1628
🎉 All tests passed! |
|
Original author: @github-actions[bot] Test Results (Node.js 22)test: Run #1633
🎉 All tests passed! |
|
Original author: @github-actions[bot] Test Results (Node.js 22)test: Run #1635
🎉 All tests passed! |
|
Original author: @github-actions[bot] Test Results (Node.js 22)test: Run #1637
🎉 All tests passed! |
|
Original author: @github-actions[bot] Test Results (Node.js 22)test: Run #1649
🎉 All tests passed! |
|
Original author: @github-actions[bot] Test Results (Node.js 22)test: Run #1650
🎉 All tests passed! |
|
Original author: Wassim Chegham (@manekinekko) Portal UI Review✅ What looks good
Issues to flag1. Duplicated badge rendering logic (3 places)
2. Inconsistent labels between badge and card 3. activeRun.turns!.filter(t => t.atifUrl).lengthThis is called twice with 4. No error/retry UX for failed enrichment 5. ATIF download opens raw blob URL in new tab window.open(api.atifUrl(runId, turn.iteration), "_blank");This opens JSON in a blank tab. A proper file download (via 6. Grouped rows show dash for enrichment Post-Processor Worker Review
Event-Driven Dispatcher Review
API Refactoring Review
Shared Package & Migrations Review
No breaking changes to shared types, queue processor, retry logic, or blob storage. ✅ Infrastructure & Deployment Review✅ No issues found. Env vars, resource limits, queue names, Docker, kustomize, and service dependencies all follow established patterns correctly. Summary
Top priority fixes: the HAR null pointer crash (API), the queue send orphaning runs (dispatcher), and the |
Original author: Cedric Vidal (@cedricvidal)
Summary
Implements #780 — ATIF (AI Tool Interaction Format) file generation via a new post-processor worker, along with Portal UI, API refactoring, resilience utilities, and developer skills.
Key Changes
ATIF Post-Processor Worker (
apps/workers/post-processor/)atifactpackage to convert HAR recordings into the standardized ATIF v1.7 formatatif.trajectory.jsonto blob storage per iterationrm(recursive)instead ofunlinkEvent-Driven Post-Processing Dispatch
run.postProcessorStatus: "queued"atomically in the same DB writePostProcessorDispatcherpolling loop repurposed as backfill-only (interval increased from 2s to 30s, batch 10 → 30)onRunTerminalhook inBaseQueueProcessorfor error-path dispatchpostProcessorStatusvia$unsetifsendMessagefails after all SDK retriesPortal UI
<EnrichmentBadge>component — extracted reusable badge with consistent labels across all usages (Pending / Enriching… / Enriched / Failed)API Endpoint Refactoring
registerArtifactRoutes()helper — generic route-pair registration for request-level and per-run artifact downloads (ATIF, HAR, tool-calls, snapshots)downloadBlobToResponse()helper — shared blob download logic with Content-Type/Disposition/Length handlingresource.runin artifact-route (prevents crash on requests without run data)ATIF API Endpoints
GET /api/v1/requests/:id/atif?iteration=N— download ATIF for a requestGET /api/v1/requests/:id/runs/:runId/atif?iteration=N— download ATIF for a specific runiterationparameter is mandatory (returns 400 if missing)Resilience Utilities
@Retrydecorator — exponential backoff with configurablemaxRetries,baseDelayMs, andisRetryablepredicate (packages/shared/src/resilience/)withRetry()function — standalone retry wrapper for non-class contextsdocs/architecture/retry.mdReport Generator
get_atif_trajectorytool so reports can reference ATIF dataRequestDocumentInfrastructure
register-version-post-processorinit service for version registrationrequired: falseon mongodb dependency for shared infra modeDocumentation
docs/architecture/post-processing.md— pipeline architecture with Mermaid diagrams, handler extensibility, version managementdocs/architecture/retry.md— retry utilities referenceDeveloper Skills
.agents/skills/resilience/).agents/skills/typescript/).agents/skills/cosmosdb-mongodb/).agents/skills/documentation/)Testing
atif.test.ts)Closes #780