Skip to content

Pipeline internals ​

This page documents the story pipeline for operators and contributors: the stages, which actor runs at each, how the orchestrator routes between them, and the recovery paths. The canonical sources are src/core/state-machine/decide-transition.ts (the transition truth table) and docs/ARCHITECTURE.md §5 — refer to those when extending the machine.

The linear walk ​

Think → Plan → [APPROVAL] → Build → Review → Test → Ship → ⟶ [PR outcome] ⟶ Reflect
StageActorInputsOutputHuman gate
ThinkArchitect (Think turn)ADO story + project memory (<repo>/.vlx/memory/)stories/<id>/spec.md (problem, AC, constraints, open questions)only if it asks a question
PlanArchitect (Plan turn)frozen spec.mdstories/<id>/plan.md (file list, AC traceability, test approach, docs impact, rollback)no
APPROVALnone (gate)plan.mdapprove / rejectoptional — off by default (approval_gate.enabled)
BuildBuilderspec.md, plan.mdcode + tests + docs committed on vlx-bot/<id>; .vlx/<id>/build-result.jsononly if it needs a credential or asks
ReviewCriticdiff vs base, plan.mdstories/<id>/review-findings.json (strict)no
TestInspectorspec.md, plan.md, review-findings.jsonstories/<id>/{qa-plan.md, qa-evidence/, qa-result.json}no
Shipnone (orchestrator)branch + all artifactsPR opened, ADO story linked, Telegram status postedno
ReflectReflector (async)event log + artifacts + PR-status eventsstories/<id>/reflection.json (ground-truth signals + proposed corrections)no

intake (the queue-claim step) is not a pipeline stage — it's how the orchestrator claims a story off the queue.

Routing & outcomes ​

Each stage emits a structured outcome; the orchestrator's MECE transition table (decide-transition.ts) maps (stage, outcome) to the next move. Outcomes:

OutcomeMeaningTypical routing
completedstage produced a valid artifactadvance to the next stage
deferredawaiting human input (a question fired)suspend; resume on the human's reply
failedthe stage could not completeterminal-fail (subject to auto-escape)
escalatedfail-closed: the harness can't trust the resultneeds human review
plan_inconsistentimplementation diverged from the approved planre-plan → re-enters APPROVAL
coverage_incompletea declared AC has no test coverageone Build fix-up attempt on its own budget
mockup_mismatchimplemented UI doesn't match an attached mockupone Build fix-up attempt, then flag-and-ship

Fix-up budgets. coverage_incomplete and mockup_mismatch each grant a single Build retry on a dedicated budget; if still unresolved the run ships flagged (loud warning in Telegram + ADO + PR body) rather than terminal-failing — a human judges it at PR review.

Deviation → re-plan. If Build deviates from the approved plan, the run re-enters Plan, which re-enters the APPROVAL gate — the revised plan returns to the human before further code changes (posted non-blocking so the operator is informed without stalling).

Post-Ship: event-driven, not linear ​

The linear walk ends at Ship (PR opened). After that, the PR-status poller (src/daemon/pr-status-poller.ts) polls each tracked PR every 60 s and emits events:

EventHandlerAction
pr_status_changed (vote)daemonTelegram nudge + a /respin hint — votes do not auto-respin
pr_comment_addedResponder actorone-shot read-only classification → in-thread reply
10-min comment quiescencemaybeAutoRespinnew Build run addressing the whole comment batch; push to the open PR
merged / abandonedreflect handlerun-track PR, record outcome, run Reflector → reflection.json → [brain] PR

Per-comment Responder. Each new reviewer comment triggers a one-shot, read-only turn that classifies it (question / change_request / nit) and replies in-thread: questions get a factual answer; change requests get an agree/disagree reply (agreed ones feed the respin); nits are skipped. Replies are bot-tagged so the poller never re-triggers on them, capped at 3 automated replies per thread.

Debounced auto-respin. Once a PR has respin-eligible comments (unresolved threads the Responder agreed to change) and the reviewer has been quiet for 10 minutes (RESPIN_DEBOUNCE_MS), the poller auto-triggers a respin. New comments reset the window; an active run blocks re-fire; /respin <storyId> bypasses the wait.

Fast-path profile ​

Every story pays the full six-stage pipeline by default. For small or docs-only work the operator can collapse and trim the pipeline with ADO tags — a deterministic, tag-gated profile. No LLM decides the path (profile resolution is a pure function of the story's tags, the same zero-model style as the Designer/Sentinel triggers), and untagged stories are byte-for-byte unaffected — they resolve to the full profile and every stage behaves exactly as above.

TagEffect
vlx-smallThink + Plan collapse into one Architect session (produces both spec.md and plan.md, both verified in that turn); the normal APPROVAL gate is kept; Build is kept; Review runs as a shallow single pass (the /codex cross-model second opinion is skipped); Test is kept.
vlx-skip-reviewThe Review stage is bypassed entirely.
vlx-skip-testThe Test stage is bypassed entirely — for docs-only / non-code changes only (no gates run).

Tags are case/bracket-insensitive (vlx-small, VLX-Small, [vlx-small] all match) and independent — the skip tags work with or without vlx-small (e.g. a docs-only story tagged only vlx-skip-test). Tags are frozen at intake, so the profile is stable across the whole run.

No new outcomes. Every fast-path effect resolves to an ordinary completed outcome — the decide-transition.ts table is unchanged. vlx-small collapses work into the Think stage (plan.md is committed in the Think checkpoint, so recovery is safe; if it's absent post-restore the Plan stage regenerates it via the normal turn). The skips short-circuit their stage before the actor runs.

Recorded and disclosed. Every skip appends a stage_skipped audit event to the run, and the Ship stage prepends a Pipeline profile section to the PR description — naming the profile, the combined Think+Plan note, the review depth, and a prominent ⚠ line for any skipped stage (Test SKIPPED … no gates ran). The Think handler records the resolved profile once via a pipeline_profile_resolved event.

APPROVAL is kept, not forced. vlx-small keeps the normal approval gate (governed by approval_gate.enabled); it never removes approval and never forces it on when the project has it globally disabled. Operators using the fast-path are advised to keep approval_gate enabled — it is the one human checkpoint the profile preserves.

A global kill-switch (fast_path.enabled, default true) ignores all three tags when set to false. See Configure — fast_path.

Operator commands & auto-escape ​

Telegram slash commands (src/daemon/telegram-commands.ts):

  • /respin <storyId> — respin open-PR comments now (bypasses the debounce)
  • /requeue <storyId> — revive a failed story from where it died, fresh retry budget
  • /restart <storyId> <stage> — re-run from an arbitrary stage
  • /cancel <storyId> — kill the active run (excluded from auto-escape)
  • /history <storyId> — last 30 events of the story's latest run

Auto-escape (src/core/state-machine/auto-escape.ts): a pre-Ship failure lands the story in queue state failed, which no claim loop re-picks. A daemon sweep autonomously requeues such stories up to 2 times per story (counted via auto_escape_triggered events), skipping cancelled runs, already-shipped stories, and stories whose ADO work item is no longer New/Active. At the cap the run is marked needs_review and Telegram is notified once.

A session-limit rate limit is excluded from this budget entirely — it pauses the run (waiting, auto-resumes) rather than failing it. See Run a story — Rate-limit pauses.

Artifact convention ​

Path helpers live in src/core/paths.ts:

  • <worktree>/stories/<id>/ — durable story artifacts (spec, plan, review-findings, qa-*, reflection); part of the story PR. No per-attempt segment — retries reuse the same folder.
  • <worktree>/.vlx/<id>/ — gitignored intermediate state (build-result.json handoff, scratch, per-stage checkpoint manifests).
  • Branch vlx-bot/<id> — one branch (and worktree) per story; retries resume from its tip.
  • Client-repo project memory at <client-repo>/.vlx/memory/, committed.

Internal Veloxcore tool — not a public product.