Skip to main content

Architecture: Pipeline Overview

Status: Active Last updated: 2026-05-02 Audience: AI agents and engineers reasoning about how a work generation request flows from API to GitHub repos. The companion pipeline-executor covers the runtime substrate (state machine, modifiers, cancellation, checkpointing); this spec is the wide-angle view across all pipeline categories.


1. Purpose

A work generation transforms a user's prompt + configuration into three GitHub repositories: a data repo (YAML config + JSON items), a markdown repo (README + per-item detail pages), and a website repo (Next.js application). The pipeline is the unit of work that produces the items; the data/markdown/website generators persist them.

Crucially, "the pipeline" is plural — the platform ships several distinct pipeline plugins. Some run a fixed sequence of steps inside the platform's executor; others delegate the entire generation to an external system and report back. This spec covers the full taxonomy and the routing logic that picks one per run.

2. Generation as a Three-Stage Orchestrator

User / Schedule / API


WorkGenerationService.startGeneration(...)
│ creates WorkGenerationHistory
│ builds WorkGenerationPayload

WORK_GENERATION_DISPATCHER


Trigger.dev workGenerationTask


TriggerGenerationOrchestrator.run({...})

┌──────────────────────┼──────────────────────────┐
▼ ▼ ▼
DataGeneratorService MarkdownGeneratorService WebsiteGeneratorService
│ │ │
│ │ │
▼ ▼ ▼
data repo markdown repo website repo
(commit/PR) (commit/PR) (auto-init or sync)

Stage gating inside the orchestrator (see packages/tasks/src/trigger/worker/orchestrators/trigger-generation.orchestrator.ts):

StageRuns when
Data generatorAlways
Markdown generatornewItemsCount > 0 || updatedItemsCount > 0
Website generatornewItemsCount > 0 || hasExistingItems (skip if none)

Cancellation flows through signal: AbortSignal — every stage and every step inside DataGeneratorService checks throwIfGenerationCancelled(signal) at safe boundaries.

3. Where the Pipeline Plugin Plugs In

DataGeneratorService.initialize is the entry point that invokes a pipeline. It hands off to ItemsGeneratorService.generateItems, which routes through PipelineOrchestratorService.execute:

// packages/agent/src/pipeline/pipeline-orchestrator.service.ts
const plugin = await this.resolvePipelinePlugin(pipelineId, workId, userId);
const mode = isStepOrchestratablePipeline(plugin) ? 'step' : 'full';

if (mode === 'step') {
return this.stepExecutor.execute(plugin, work, request, existing, options, onProgress);
}
return this.fullExecutor.execute(plugin, work, request, existing, options, onProgress);

Two execution modes, set by the pipeline plugin's own type signature (isStepOrchestratablePipeline(plugin)):

ModeExecutorUsed by
stepStepPipelineExecutorServicestandard-pipeline (the only built-in step-orchestratable pipeline today)
fullFullPipelineExecutorServiceEvery other pipeline plugin — they own their entire flow and report a result back

All pipeline plugins implement IPipelinePlugin from @ever-works/plugin. Step-orchestratable plugins additionally implement IStepOrchestratablePipelinePlugin and expose a step registry the executor walks.

4. The Four Pipeline Categories Today

pipeline-plugin (capability)

├── step-orchestratable
│ └── standard-pipeline ← 15-step canonical pipeline (this spec's §6)

├── agent-driven (full mode)
│ ├── agent-pipeline ← Vercel AI SDK ToolLoopAgent over the same facades
│ ├── claude-managed-agent ← Anthropic's Agents SDK
│ ├── claude-code ← Spawn Claude Code CLI in a worker subprocess
│ ├── codex ← Spawn OpenAI Codex CLI
│ ├── gemini ← Spawn Google Gemini CLI
│ └── opencode ← Spawn opencode CLI

└── external-platform (full mode)
├── make ← Trigger a Make.com scenario
├── sim-ai ← Sim.ai workflow
└── zapier ← Zapier zap

All ten pipeline plugins are first-party packages under packages/plugins/. They're selectable per work via the plugin-settings cascade described in plugin-sdk §11: work override → user default → platform default, with standard-pipeline as the platform default.

The choice surfaces in the dashboard's "Generation method" picker on the work detail page; settings hygiene (secrets, x-secret, plugin enable/disable) is handled by settings-system.

5. Pipeline Plugin Selection Cascade

1. work-pinned plugin id (most specific — set per work)
2. user-default plugin id (set on the user's profile)
3. platform default (standard-pipeline)

PipelineOrchestratorService.resolvePipelinePlugin walks this cascade. If the resolved plugin is disabled or missing, the resolver falls through to the next level rather than failing the run; if no level resolves, the run errors with a clear "no pipeline available" message rather than silently picking a different one.

Provider overrides on request.providers (per-run) are applied inside the chosen pipeline — they pick which AI / search / extractor / screenshot plugins the steps use, not which pipeline runs.

6. The Standard Pipeline (15 Steps)

The standard-pipeline plugin owns the canonical step set. The PipelineExecutor runs steps sequentially and stops on first failure. Steps live in packages/plugins/standard-pipeline/src/steps/ and are registered in that package's steps/index.ts.

#Step filePurpose
1prompt-comparison.step.tsCompare new prompt vs the last run's prompt for similarity
2prompt-processing.step.tsExtract subject, categories, keywords from prompt
3domain-detection.step.tsClassify domain type (SOFTWARE, ECOMMERCE, etc.)
4ai-item-generation.step.tsGenerate items directly from AI (when AI-first mode is enabled)
5search-query-generation.step.tsGenerate search queries for web crawling
6web-search.step.tsExecute search queries via the search facade
7content-retrieval.step.tsFetch raw content from each result URL
8content-filtering.step.tsFilter pages by relevance threshold (LLM-judge)
9item-extraction.step.tsExtract structured items from filtered pages (LLM)
10data-aggregation.step.tsDeduplicate, merge with existing items by slug
11category-processing.step.tsAssign categories and tags
12source-validation.step.tsValidate source URLs are official + reachable
13badge-processing.step.tsEvaluate and assign badges (when badge eval is enabled)
14image-capture.step.tsCapture screenshots / smart images via the screenshot facade
15markdown-generation.step.tsGenerate per-item markdown for the markdown repo

The executor enforces type alignment between adjacent steps via the PipelineStepDefinition schemas; see pipeline-executor §3.

Step categories (for filtering / gating)

CategorySteps
analysis1, 2, 3
discovery4, 5, 6, 7
extraction8, 9, 10
enrichment11, 12, 13, 14
output15

Modifier plugins target steps by name; see pipeline-executor §6 for the modifier mechanism.

7. Agent-Driven Pipelines

Agent-based pipelines hand the entire generation problem to an LLM agent loop instead of a fixed sequence of steps. They share the input/output contract with the standard pipeline (same GenerationRequest in, same PipelineResult out) but skip the step executor entirely.

PluginWhat it runs
agent-pipelineA Vercel AI SDK ToolLoopAgent armed with the platform's facades as tools — search, extract, screenshot, etc.
claude-managed-agentAnthropic's Agents API with the same facade tools
claude-codeSpawns the Claude Code CLI in a worker subprocess; agent reads/writes files in a scratch dir
codexSpawns the OpenAI Codex CLI similarly
geminiSpawns the Google Gemini CLI similarly
opencodeSpawns the opencode CLI similarly

The CLI-based plugins (claude-code, codex, gemini, opencode) are functionally similar — they shell out to a code-agent CLI binary with a work-scoped working tree, watch stdout for structured output, and translate the result back into items + categories + tags. The plugins differ in which CLI they shell out to and how they parse the CLI's progress output.

The API-based agent plugins (agent-pipeline, claude-managed-agent) run in-process: no subprocess, no shell, no filesystem scratch. They use facades exposed as agent tools and let the LLM decide which tool to call when.

8. External-Platform Pipelines

make, sim-ai, zapier delegate the work to an off-platform workflow engine:

  1. The plugin packages the GenerationRequest into the platform's webhook format.
  2. Triggers the user's pre-configured workflow on the external platform.
  3. Polls (or receives webhook callback) for the result.
  4. Translates the result back into a PipelineResult.

These are useful when teams already have a Make scenario or Zapier zap that does the bulk of their data work — the platform integrates with the existing automation rather than asking them to recreate it inside the platform.

9. The GenerationRequest and PipelineResult Contract

Both step and full executors take the same input shape:

interface GenerationRequest {
workId: string;
userId: string;
prompt: string;
mode: 'create' | 'update';
config: {
max_search_queries?: number; // Standard pipeline only
max_results_per_query?: number;
max_pages_to_process?: number;
relevance_threshold_content?: number;
ai_first_generation_enabled?: boolean;
content_filtering_enabled?: boolean;
badge_evaluation_enabled?: boolean;
// ... full DTO in CreateItemsGeneratorDto
};
providers?: {
pipeline?: string; // pipeline plugin id override
ai?: string; // AI provider plugin id
search?: string;
screenshot?: string;
contentExtractor?: string;
};
}

interface PipelineResult {
items: ItemData[];
categories: Category[];
tags: Tag[];
brands: Brand[];
metrics: ItemsGeneratorMetrics; // tokens, cost, durations
warnings?: string[];
contentCache?: Map<string, string>;
}

This shared contract is why a work can switch from standard-pipeline to claude-code (or vice versa) without any schema migration — the data repository writer doesn't know or care which pipeline produced the items.

10. Bound Facades for Pipeline Execution

PipelineFacadeService constructs a per-run StepExecutionContext where every facade (AI, search, screenshot, content-extractor, data-source, prompt) is bound to the work + user + provider overrides. Step authors call ctx.aiFacade.askJson(prompt, schema) without ever passing workId / userId themselves:

// packages/agent/src/pipeline/pipeline-facade.service.ts
createStepExecutionContext(work, providerOverrides, aiModelOverride, signal): StepExecutionContext {
return {
aiFacade: this.createBoundAiFacade(facadeContext),
searchFacade: this.createBoundSearchFacade(facadeContext),
screenshotFacade: this.createBoundScreenshotFacade(facadeContext),
contentExtractorFacade: this.createBoundContentExtractorFacade(facadeContext),
dataSourceFacade: this.createBoundDataSourceFacade(facadeContext),
promptFacade: this.createBoundPromptFacade(facadeContext),
logger: stepLogger,
work,
user: work.user,
signal,
};
}

This isolation is what lets full-mode pipelines (agent / external) share infrastructure with the standard pipeline — they receive the same bound facades and the same cancellation signal.

11. Run Lifecycle and Persistence

WorkGenerationService.startGeneration
├── insert WorkGenerationHistory(historyId, NOT_STARTED)
├── build WorkGenerationPayload
└── WORK_GENERATION_DISPATCHER.dispatch(payload)

▼ (Trigger.dev)
TriggerGenerationOrchestrator.run({ work, user, dto, historyId, ... })
├── recordGenerationStartTime + updateGenerateStatus(GENERATING)
├── DataGeneratorService.initialize(...) ← runs the pipeline
│ ├── ItemsGeneratorService.generateItems
│ │ └── PipelineOrchestratorService.execute
│ ├── merge with existing data + write data repo
│ └── return { stats, prUpdate?, warnings }
├── if items changed → MarkdownGeneratorService.initialize(...)
├── if items exist → WebsiteGeneratorService.initialize(...)
└── updateGenerateStatus(GENERATED) + updateGenerationHistory(...)

Three classes of progress signal flow back during the run:

  1. Step-status events (per-step start/end, metrics) — emitted by the executor; consumed by the activity-log writer and the dashboard's progress view.
  2. Recent logs ring buffer — written to work.recentLogs for the live tail.
  3. Trigger.dev run logs — every NestJS log line surfaces in the Trigger.dev dashboard via createTriggerLogger(...).

On completion, the history row stores the per-step metrics (cost, tokens, durations) so the user can see exactly what each step cost.

12. Configuration Surface

The user-tunable knobs live in CreateItemsGeneratorDto. The most impactful ones for the standard pipeline:

FieldDefaultEffect
max_search_queries10Limits step 5 fan-out
max_results_per_query5Limits step 6 search-result list size
max_pages_to_process10Caps content retrieval after filtering
relevance_threshold_content0.6LLM relevance gate in step 8
ai_first_generation_enabledfalseSkip web search; rely on step 4 only
content_filtering_enabledtrueToggle step 8 entirely
badge_evaluation_enabledfalseToggle step 13
generation_methodCREATE_UPDATERECREATE wipes items before write
update_with_pull_requestfalsePush direct vs open PR
website_repository_creation_methodDUPLICATEUsed by stage 3 (website generator)

Plus the seven advanced-prompt overrides (see features/advanced-prompts) which append per-step custom instructions for steps 4, 5, 8, 9, 10, 11, and 12.

13. Reading Order for Newcomers

  1. This spec — wide-angle: stages, categories, routing.
  2. pipeline-executor — runtime substrate: state machine, modifiers, cancellation, checkpointing.
  3. plugin-sdk — how plugins are loaded, selected, and given context.
  4. trigger-integration — how the Trigger.dev wrapper delivers payloads to the orchestrator.
  5. features/data-generator, features/markdown-generator, features/website-generator — the three stages in detail.

14. File Index

apps/api/src/works/services/
├── work-generation.service.ts # Entry point, history record creation

packages/agent/src/
├── tasks/
│ ├── work-generation-dispatcher.ts # DI symbol for dispatch interface
│ └── work-generation.types.ts # Payload typings
├── items-generator/
│ ├── items-generator.service.ts # Calls into the orchestrator
│ └── interfaces/pipeline.interface.ts # GenerationContext + AdvancedPromptsContext
├── pipeline/
│ ├── pipeline-orchestrator.service.ts # Step vs full mode routing
│ ├── pipeline-builder.service.ts # Builds executor + modifier merge
│ ├── pipeline-facade.service.ts # Bound facades for steps
│ ├── step-pipeline-executor.service.ts # Wraps standard pipeline
│ └── full-pipeline-executor.service.ts # Wraps full-mode pipelines
├── data-generator/
│ ├── data-generator.service.ts # Stage 1
│ └── data-repository.ts
├── markdown-generator/
│ ├── markdown-generator.service.ts # Stage 2
│ ├── readme-builder.ts
│ └── markdown-repository.ts
└── website-generator/
├── website-generator.service.ts # Stage 3
├── website-update.service.ts
└── branch-sync.service.ts

packages/plugins/standard-pipeline/src/
├── standard-pipeline.plugin.ts # IStepOrchestratablePipelinePlugin impl
└── steps/ # 15 step implementations

packages/plugins/{agent-pipeline,claude-code,claude-managed-agent,codex,gemini,opencode,make,sim-ai,zapier}/
└── *.plugin.ts # IPipelinePlugin impls (full mode)

packages/tasks/src/
├── tasks/trigger/work-generation.task.ts # Trigger.dev task
└── trigger/worker/orchestrators/trigger-generation.orchestrator.ts # 3-stage orchestrator

15. See Also