From 2fae571e64f2486ca4175d019c202c0d8de9ec59 Mon Sep 17 00:00:00 2001 From: WilcoLouwerse Date: Tue, 29 Sep 2026 15:45:22 +0200 Subject: [PATCH 1/2] docs(academy): hydra parts 1-3 describe the pipeline the flows run The parts on concept, pipelines and gates still described the retired shell pipeline: - a ready-to-build trigger - automatic hand-off from build to review to applier - an applier skip rule - a post-review quality recheck - reviews/.json - *:running labels - yolo auto-merge - "22 gates" Checked against ConductionNL/hydra flows/ (identical on main and development): - Part 1: build:queued plus the change named in the issue body is the trigger. It gives the end-to-end path (build, gates, one fix pass, a PR on pass and fail, review only on code-review:queued, a human merges) and no longer promises lead times or model env vars. - Part 2: the build lane with its four outcomes, the review lane routed on hydra-verdict.json, and nothing queuing code review after a build. Also one unit of work per tick, the slot pool and lock, the labels no flow acts on, and a blocked review re-running every tick. - Part 3: about 96 declared gates and the COVERAGE line. The runner is a delegator to conduction/hydra-gates. The only re-gate is after the build's fix pass, and the false-positive example is real gate history. English and Dutch; the Dutch pages had drifted further and now say the same as the English ones. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../index.mdx | 94 +++---- .../index.mdx | 222 +++++++++-------- .../index.mdx | 95 +++---- .../index.mdx | 116 +++++---- .../index.mdx | 233 ++++++++++-------- .../index.mdx | 187 ++++++++------ 6 files changed, 535 insertions(+), 412 deletions(-) diff --git a/academy/2026-05-12-hydra-tutorial-1-wat-is-hydra/index.mdx b/academy/2026-05-12-hydra-tutorial-1-wat-is-hydra/index.mdx index 1f8ee05b9..d7435beba 100644 --- a/academy/2026-05-12-hydra-tutorial-1-wat-is-hydra/index.mdx +++ b/academy/2026-05-12-hydra-tutorial-1-wat-is-hydra/index.mdx @@ -5,7 +5,7 @@ contentType: tutorial heroIcon: pipeline authors: [conduction] date: 2026-05-12 -summary: A short introduction to Hydra, Conduction's agentic CI/CD platform. What it does, why it exists, and where it sits inside our app factory. The first of six short modules. +summary: A short introduction to Hydra, Conduction's agentic CI/CD platform. What it does, why it exists, and where it sits inside our app factory. The first of seven short modules. tags: [Hydra, AI, CI/CD, Tutorial series] apps: [] durationMinutes: 10 @@ -24,13 +24,13 @@ Hydra runs on OpenSpec changes. If you're not yet familiar with terms like **spe A large part of Hydra's internals leans on **Claude Skills**: `opsx-*`, `hydra-gate-*`, `team-*`, `test-*`. If you've never written or read a skill, take [part 1 of the Claude Skills tutorial series](/academy/claude-skills-tutorial-1-what-are-claude-skills) first (10 minutes). That makes part 4 of this series — where we open up the skill families inside Hydra — a lot easier to follow. ::: -Hydra is Conduction's internal **agentic CI/CD platform**: a factory that turns OpenSpec proposals into reviewed, tested code on a feature branch. This module explains in ten minutes *what* Hydra is, *why* it exists, and how it fits inside our app factory. It is the first part of a six-part series; by the end you're ready for part 2 on the three pipelines. +Hydra is Conduction's internal **agentic CI/CD platform**: a factory that turns OpenSpec changes into code on a feature branch, checked by mechanical gates and, when you ask for it, reviewed by three AI stages. This module explains in ten minutes *what* Hydra is, *why* it exists, and how it fits inside our app factory. It is the first part of a seven-part series; by the end you're ready for part 2 on the three pipelines. {/* truncate */} What Hydra is in one sentence, and why we built it. - The four container personas at a high level: Al Gorithm, Juan Claude, Clyde, Axel. + The four personas at a high level: Al Gorithm, Juan Claude, Clyde, Axel. When to use Hydra, and when not to. @@ -54,54 +54,64 @@ Hydra is Conduction's internal **agentic CI/CD platform**: a factory that turns Before we dive in, a quick orientation that will make every later module easier to read. Two Conduction repos come up constantly across the series, and they have **separate jobs**: -- **[`ConductionNL/hydra`](https://github.com/ConductionNL/hydra)** *(private)* — the **factory**. Container images, agent personas (Al Gorithm, Juan Claude, Clyde, Axel), orchestration scripts, the `.claude/skills/` catalogue (`hydra-gate-*`, `opsx-*`, `test-*`), OpenSpec changes. Everything that *runs*. Clone it only if you want to run Hydra locally or modify a skill. -- **[`ConductionNL/.github`](https://github.com/ConductionNL/.github)** *(public)* — the **manual**. Developer guides, way-of-work, ISO compliance, the public Hydra one-pager you're (optionally) reading as warm-up, and shared tooling like the global Claude settings. Everything you *read or install once*. +- **[`ConductionNL/hydra`](https://github.com/ConductionNL/hydra)** *(private)* — the **factory**. The pipeline flows (`flows/`), the agent personas (Al Gorithm, Juan Claude, Clyde, Axel), the remaining operator scripts, the `.claude/skills/` catalogue (`hydra-gate-*`, `opsx-*`, `test-*`), OpenSpec changes. Everything that *runs*. Clone it only if you want to run Hydra locally or modify a skill. +- **[`ConductionNL/.github`](https://github.com/ConductionNL/.github)** *(public)* — the **manual**. Developer guides, way-of-work, ISO compliance, the public Hydra one-pager you're (optionally) reading as warm-up, and shared tooling: the global Claude settings, and the `conduction/hydra-gates` package that holds the gate logic (part 3). Everything you *read or install*. -**Hydra is the factory, `.github` is the manual.** Two repos, two purposes, no overlap. The full breakdown lives in [`.github/docs/hydra/README.md`](https://github.com/ConductionNL/.github/blob/main/docs/hydra/README.md#hydra-repo-vs-github-repo). +**Hydra is the factory, `.github` is the manual.** Two repos, two purposes. The full breakdown lives in [`.github/docs/hydra/README.md`](https://github.com/ConductionNL/.github/blob/main/docs/hydra/README.md#hydra-repo-vs-github-repo). ## In one sentence -Hydra is a **stateless, label-driven pipeline** that turns OpenSpec proposals into feature branches with code, tests, a review history and a PR — ready for human approval. +Hydra is a **label-driven pipeline** that turns an OpenSpec change into a feature branch with code and a PR carrying the gate results — ready for human review and approval. -Not an "AI assistant that helps the developer", but a **factory**. You throw an OpenSpec change in one end and out the other end rolls a PR that two independent reviewers have looked at. +Not an "AI assistant that helps the developer", but a **factory**. You put an OpenSpec change in one end and a PR comes out the other end. Hydra never merges it. Concretely: Hydra works on the `openspec/changes//` folder — `proposal.md`, `design.md`, `tasks.md` and the spec delta. If those terms are new, take the OpenSpec series first (see tip at the top). - You prepare an OpenSpec change (by hand or with /opsx-ff), then label its tracking issue ready-to-build (or <prefix>-ready-to-build when a label prefix is set). That single label is the entire trigger. The builder then reads design.md (what and why), everything under specs/ (acceptance criteria), and tasks.md (the concrete checklist), and implements every unchecked [ ] item. Nothing else starts a build — the label is the contract between OpenSpec and Hydra. + You prepare an OpenSpec change (by hand or with /opsx-ff) and commit it on the repo's development branch. You name it in the tracking issue's body (openspec/changes/<slug>) and label that issue build:queued. That label is the trigger. The hydra-sequencer flow finds the issue, reads the change name from the issue body, checks that the change directory exists, and has an agent implement it from proposal.md, tasks.md, design.md and specs/. An issue that names no change, or names a change that isn't there, gets build:blocked instead of a build. +## How a change travels, end to end + +A short overview; part 2 goes through every step. + +1. **Build.** An agent implements the change in a checkout of the base branch. The pipeline, not the agent, commits and pushes the result to `feature//hydra-build`. +2. **Gates.** The pipeline runs the `hydra-gates` suite over the diff. If any gate fails, there is **one** fix pass: the agent gets the gate output and fixes only what it names, and the gates run once more. +3. **PR.** Hydra opens a PR whether the gates ended green (`build:pass`) or red (`build:fail`). The gate output is in the PR body. +4. **Review, when asked.** Nothing queues a review automatically. When someone labels the issue `code-review:queued`, three stages run one after another, each only if the previous one passed: code review, security review, applier. +5. **A human merges.** No flow merges a PR, and no flow sets a `done` label. + ## Why does Hydra exist? Conduction builds a lot of open-source apps for the Nextcloud workplace. The rhythm of that factory is determined by two things: how fast we can write **changes**, and how fast those changes become **code**. Hydra automates that second step, with three explicit goals: -1. **Shorten lead time.** Without Hydra, an average OpenSpec proposal takes half a day to three days to implement. With Hydra, it's half an hour to a few hours, depending on complexity. -2. **Lock in quality.** No PR leaves the system without mechanical quality gates (PHPCS, Psalm, PHPStan, ESLint, PHPUnit), an independent code review and an independent security review. Two pairs of AI eyes before a human looks. -3. **Preserve the four-eyes principle.** Hydra **does not merge on its own** — except when an issue explicitly carries the `yolo` flag, and even then all gates have to be green. A human presses the button. +1. **Shorten lead time.** The step from a written change to a first reviewable PR is done by an agent, not by hand. +2. **Lock in quality.** Every build runs the mechanical `hydra-gates` suite before its PR is opened, and the PR shows the result. On top of that sit three independent AI review stages you can queue. +3. **Preserve the four-eyes principle.** Hydra **does not merge**. There is no auto-merge path: a human reads the PR and presses the button. ## The four personas -Internally, Hydra is built from four ephemeral container personas. Each plays one role, in one container, with scoped credentials and tight turn budgets. You don't have to memorise their names now — they come back in part 2 — but it helps to spot the role pattern early: +Hydra has four personas. Each plays one role and has its own brief under `agents//`. You don't have to memorise their names now — they come back in part 2 — but it helps to spot the role pattern early: - - Builds the code from the change. Never sees reviews. Stops after a fixed turn budget. + + Builds the code from the change. Never sees reviews. Has no shell and no forge credential: it edits files, and the pipeline commits and pushes them. - - Reads only the PR diff. Can apply mechanical fixes within scope. + + Reviews the PR diff. His brief allows small, mechanical fixes within scope. - - Runs after Juan Claude. Same shape, plus Semgrep and pattern matching on CWE classes. + + Runs after Juan Claude, and only when code review passed. Same shape, with a security brief: CWE classes such as injection, XSS, path traversal and hardcoded secrets. - - Decides binarily: does this pass or not? Reads both reviews and the final code. No fix tools. + + Decides binarily: does this pass or not? Runs after a passed security review. His brief gives him no fix authority. -Important: **all four personas default to Sonnet**, but with different fallbacks. The builder falls back to **Haiku** (cheaper, for budget pressure); the three judgement roles fall back to **Opus** (deeper, when Sonnet quota is exhausted). The models are configurable per role via `HYDRA_BUILDER_MODEL` / `HYDRA_REVIEWER_MODEL` env vars. +About models: each persona's `agents//config.yaml` names **Sonnet**. The flows themselves pass no model flag, so a stage runs on whatever model the hermiq runner that executes it is configured with. Environment variables such as `HYDRA_BUILDER_MODEL` do not steer the flows; `HYDRA_REVIEWER_MODEL` is read only by the manual `scripts/manual-review.sh`. -> **History note:** an earlier design ran the builder on **Haiku** by default — building looked like cheap pattern-following. In practice Haiku was caught *skipping quality gates* despite explicit instructions, so the builder's default was raised to Sonnet, with Haiku kept only as a fallback. If you read older docs that say "builder = Haiku", this is why they're out of date. +> **History note:** an earlier design ran the builder on **Haiku** — building looked like cheap pattern-following. In practice Haiku was caught *skipping quality gates* despite explicit instructions, so the builder's config was moved to Sonnet. If you read older docs that say "builder = Haiku", this is why they're out of date. ## When to use, when not @@ -111,7 +121,7 @@ Hydra is no silver bullet. The rule of thumb: - The work fits inside one coherent OpenSpec change (one feature, not a refactor of the whole system). - The acceptance criteria can be checked mechanically (tests exist, or can be written quickly with PHPUnit/Newman/Playwright). -- The target repo is **ready** for Hydra: has the `hydra-gate-*` labels, an `app-config.json`, and runs the standard quality suite. +- The target repo is **ready** for Hydra: it has the stage labels (`scripts/create-labels.sh` creates them), the change is committed on its `development` branch, and the `hydra-gates` suite gives a meaningful result on it. **Don't use it when:** @@ -127,7 +137,7 @@ A common mistake: thinking Hydra invents your whole product. The opposite is tru - **Hydra doesn't write changes (or specs).** Those are provided up front, or a human writes them by hand (with `/opsx-ff`). - **Hydra doesn't do vision or strategy.** What features we build is a human choice. - **Hydra doesn't decide architecture.** That's what ADRs are for, maintained by humans (see `hydra/openspec/architecture/`). -- **Hydra doesn't merge on its own authority.** Except for `yolo` issues — and there, all gates are still hard requirements. +- **Hydra doesn't merge.** Not after a green build, not after three passed reviews, and not for an issue labelled `yolo`: no flow reads that label. ## Test yourself @@ -139,17 +149,17 @@ Four short questions to check whether you've understood this part. Stuck? Click
-Hydra does one specific step in our factory. Which step, and which two benefits does that deliver? +Hydra does one specific step in our factory. Which step, and what does it hand over at the end?
-Hydra automates the step from **OpenSpec change to reviewed feature branch + PR**. Three explicit goals: +Hydra automates the step from **OpenSpec change to feature branch + PR**. Three explicit goals: -1. **Shorten lead time** — from half a day to three days by hand, down to half an hour to a few hours. -2. **Lock in quality** — mandatory mechanical gates (PHPCS, Psalm, PHPStan, ESLint, PHPUnit) + two independent AI reviews. -3. **Preserve the four-eyes principle** — Hydra doesn't merge itself; a human presses the button. Exception: `yolo` issues, and even then all gates must be green. +1. **Shorten lead time** — an agent does the implementation step, not a person. +2. **Lock in quality** — every build runs the mechanical `hydra-gates` suite and the PR shows the result; three AI review stages can be queued on top. +3. **Preserve the four-eyes principle** — Hydra never merges; a human presses the button. There is no exception for `yolo`.
@@ -157,7 +167,7 @@ Hydra automates the step from **OpenSpec change to reviewed feature branch + PR*
-**2. Why does Hydra rely on mechanical quality gates (PHPCS, Psalm, PHPStan, ESLint, PHPUnit) rather than only AI review?** +**2. Why does Hydra rely on mechanical quality gates rather than only AI review?**
@@ -167,10 +177,10 @@ AI reviewers can sound convincing without being factually correct. Mechanical ch
-Mechanical gates are **deterministic and unpersuadable**. You can't talk PHPStan around; ESLint doesn't care about a well-reasoned story. Two consequences: +Mechanical gates are **deterministic and unpersuadable**. The same diff gives the same result, and a well-reasoned story doesn't change it. Two consequences: -1. **False positives and hallucinations get filtered** before a reviewer looks — a PR that fails the gates never even reaches Juan Claude or Clyde. -2. **Standards are enforceable across the whole fleet** — code style, types and test results are objective, not dependent on who reviews that day. +1. **Every PR carries an objective result** — the gate output is in the PR body, and a build whose gates stay red is labelled `build:fail`, so a human sees that before deciding whether to queue a review. +2. **Standards are enforceable across the whole fleet** — the same gates run on every repo, whoever reviews that day. A review stage that can't show it ran the gates is not accepted as a pass. AI review sits on top, not in place of. The two layers catch different classes of problems. @@ -180,11 +190,11 @@ AI review sits on top, not in place of. The two layers catch different classes o
-**3. What are Hydra's four personas, and which one is the one that CANNOT write?** +**3. What are Hydra's four personas, and which one may NOT change code?**
-One persona is deliberately read-only — its role is a binary gate ("does this pass or not?"). It has no Write or Edit tools. +One persona's role is a binary gate ("does this pass or not?"). Its brief gives it no fix authority.
@@ -192,10 +202,10 @@ One persona is deliberately read-only — its role is a binary gate ("does this The four personas: -- **Al Gorithm** — builder (Sonnet, Haiku fallback). Writes code. -- **Juan Claude van Damme** — code reviewer (Sonnet, Opus fallback). May push bounded fixes. -- **Clyde Barcode** — security reviewer (Sonnet, Opus fallback). May also push bounded fixes. -- **Axel Pliér** — applier (Sonnet, **no write access** — Read + Bash only). Reads the final diff + both reviews and gives one binary answer: `pass: true` or `pass: false`. He judges, he doesn't intervene. +- **Al Gorithm** — builder. Writes code; the pipeline commits and pushes it. +- **Juan Claude van Damme** — code reviewer. His brief allows bounded, mechanical fixes. +- **Clyde Barcode** — security reviewer. The same, with a security brief. +- **Axel Pliér** — applier. Gives one binary answer: pass or fail. He judges, he doesn't intervene.
@@ -216,7 +226,7 @@ Think about: does the work fit in one change, is the target repo ready, and is t A few common ones: - **The work doesn't fit in one OpenSpec change** — multi-repo refactor, breaking schema change with data migration. -- **The target repo isn't ready** — no `hydra-gate-*` labels, no `app-config.json`, or an unreliable quality suite. Hydra is only as safe as its gates. +- **The target repo isn't ready** — no stage labels, no change committed on `development`, or gates you don't trust on that repo. Hydra is only as safe as its gates. - **Spike or prototype without reviewer discipline** — "just trying something out". The overhead isn't worth the gain. - **Human negotiation required** — a customer, vendor or legal point has to be resolved first before the work can begin. @@ -228,7 +238,7 @@ A few common ones: - The three pipelines (build, code review, security review) and how they hand off sequentially. - The full label state machine: build:queued → ... → done / needs-input. - What the applier (Axel Pliér) does, and why he can be skipped. - The difference between retry:queued and rebuild:queued. + The build lane and its four outcomes: build:pass, build:fail, build:no-change, build:blocked. + The review lane (code review → security review → applier) and how each verdict is routed. + How one scheduled flow, hydra-sequencer, picks one unit of work per tick. + Which labels no flow acts on, including retry:queued, rebuild:queued and yolo. @@ -34,117 +34,147 @@ In part 1 you saw that Hydra has four personas. In this part we look at the **pi A notion of what labels on a GitHub issue are and how you add them. -## Three pipelines, one pipeline +## Three pipelines, two lanes -Hydra technically has three pipelines: **build**, **code-review** and **security-review**. But in practice they always run in the same order, automatically driven by labels. Under the hood it's one state machine: +Hydra has three pipelines: **build**, **code review** and **security review**, with the applier as a third review stage after security. They are all driven by labels on the tracking issue, and they fall into two lanes. The build lane starts when you label an issue `build:queued`. The review lane starts only when someone labels it `code-review:queued` — **nothing queues it for you**, not even a green build. ``` -ready-to-build (or -ready-to-build on a dev workstation — see part 5) +build:queued ← you apply it + ↓ build → gates → (one fix pass → gates) +build:pass PR opened, gates green +build:fail PR opened, gates still red +build:no-change the agent changed nothing, nothing pushed +build:blocked no change named, change not found, or the build could not finish + +code-review:queued ← you apply it ↓ -build:queued → build:running → build:pass - ↓ -code-review:queued → :running → :pass / :fail - ↓ (always traversed — even on :fail) -security-review:queued → :running → :pass / :fail - ↓ -decision: - both reviews :pass + 0 fixes → done (Axel skipped) - both reviews :pass + ≥1 fix → applier:queued → :running → :pass / :fail - one of the reviews :fail → needs-input (Axel skipped, human decides) +code-review:pass → security-review:queued +code-review:fail → stop +security-review:pass → applier:queued +security-review:fail → stop +applier:pass → end: a human reviews and merges the PR +applier:fail → stop + +needs-input ← a review stage with no readable verdict, a verdict that + doesn't show the gates ran, or a stage that could not run ``` -The pipeline has one important rule: **every outcome after review is terminal**. Hydra doesn't automatically fix in a loop. If it succeeds in one run, great. If not, the issue lands on `needs-input` and a human has to decide the next step. More on that in part 6. +Every stop is final as far as Hydra is concerned: there is no automatic retry and no fix loop after review. A human reads the PR and the labels and decides what happens next. Part 6 goes into that. -## Pipeline 1: Build (Al Gorithm, Sonnet) +## Pipeline 1: Build (Al Gorithm) -The builder receives the change, a clean clone of the target repo, and a turn budget. No review history, no feedback from earlier runs. He implements the tasks from `tasks.md`, runs the quality suite, opens a draft PR. +The `hydra-sequencer` flow picks up an issue labelled `build:queued` and works through it in these steps: -Order within the build phase: +1. **Find the change.** The change name is read from the issue **body**: the first `openspec/changes/` it mentions. No change named → `build:blocked`. +2. **Pick the base branch.** `development` if the repo has one, otherwise the repo's default branch. The change directory must exist on that branch, otherwise → `build:blocked`. +3. **Claim capacity.** A slot from the shared pool and a per-issue lock (see [Labels](#labels-one-source-of-truth) below). +4. **Build.** An agent implements the change in a checkout of the base branch, reading `proposal.md`, `tasks.md`, `design.md` and `specs/`. It can read and edit files but has no shell and is told not to run git. The **pipeline** commits and pushes its work to `feature//hydra-build`, limited to `lib/`, `src/`, `tests/`, `openspec/`, `appinfo/`, `openapi.json`, `CHANGELOG.md` and `README.md`. If the agent changed nothing → `build:no-change`. If the build could not finish (for example because it touched a file outside that list and the push was refused) → `build:blocked`. +5. **Gates.** The pipeline runs `scripts/run-hydra-gates.sh --scope-to-diff --base origin/`. Exit code 0 means zero failures → `build:pass`. +6. **One fix pass.** Otherwise the agent gets the gate output and fixes only what the gates named. If it changes nothing → `build:fail`. If it changes something, the gates run again: 0 → `build:pass`, anything else → `build:fail`. +7. **PR.** On both `build:pass` and `build:fail` Hydra opens a PR from `feature//hydra-build` to the base branch, titled `: built by hydra for #`, with `Closes #` and the gate output in the body. It is not merged. +8. **Clean up.** `build:queued` is removed, lock and slot are released, and the stage is recorded in `hydra.json`. -1. **Implementation** — reads `proposal.md`, `design.md` and `tasks.md` from `openspec/changes//` in the target repo, writes code. -2. **Quality checks** — PHPCS, PHPMD, Psalm, PHPStan, ESLint, Stylelint, `composer audit`, `npm audit`. -3. **PHPUnit + Newman** — backend and API tests. -4. **Browser tests** — Playwright MCP walks through the UI on the host (see part 4 for how this differs from the `@e2e` gate). -5. **`fix-quality` / `fix-browser`** — if checks go red, Al Gorithm gets one or two attempts to mechanically repair. This is a *pre-review* fixup, not a review loop. -6. **Verdict** — `build:pass` (PR has been opened) or `build:fail` (broken build, `needs-input`). +The flow runs only the `hydra-gates` suite. PHPUnit, Psalm, PHPStan, Newman and browser tests are **not** part of the build lane; `scripts/run-quality.sh` and `scripts/run-browser-tests.sh` still exist, but no flow calls them. Part 3 covers what the gates do check. -Important: Al Gorithm runs on **Sonnet** by default, with **Haiku** as a cheaper fallback under budget pressure (configurable via `HYDRA_BUILDER_MODEL`). An earlier design ran the builder on Haiku by default — but Haiku was caught skipping quality gates, so the default was raised to Sonnet. See the history note in [part 1](/academy/hydra-tutorial-1-what-is-hydra). +`build:fail` does **not** lead to `needs-input`. The PR is there, with the red gate output in its body, and it's up to a human to read it. - No reviews, no verdict history, no labels other than his own build:*. So he doesn't know that an earlier reviewer rejected something — that feedback only reaches him through an explicit retry:queued cycle (see below). + No reviews, no verdict history, no labels. The build prompt carries the change and the issue; the fix pass carries only the gate output. Reviewer findings never reach the builder: no flow feeds them back (see retry and rebuild below). -## Pipeline 2: Code review (Juan Claude van Damme, Sonnet) +## Pipeline 2: Code review (Juan Claude van Damme) + +Nothing moves an issue from `build:pass` to review. You decide that, by applying `code-review:queued` — for example with the operator script, which also moves the project-board card: + +```bash +scripts/hydra-label.sh ConductionNL/ add code-review:queued +``` -Once `build:pass` is set, the `hydra-sequencer` flow automatically queues `code-review:queued`. Juan Claude picks it up: +The flows only read bare label names, so run it without a `HYDRA_LABEL_PREFIX` set. -1. Reads **only the PR diff** (`HYDRA_REVIEW_SCOPE=diff`, ADR-020). That keeps the scope manageable and ensures every line he comments on is actually touched in this PR. -2. Walks the ADR library and gate skills (see part 3). -3. For **mechanical, in-scope errors** he may fix himself (ADR-021: bounded-fix scope). Missing PHPCS headers? He adds them. Awkward variable name? Not his department. -4. For each finding he writes an inline comment on the PR with prefix `[fixed:...]` or `[unfixed:...]`. -5. He commits and pushes his fixes to the feature branch and sets `code-review:pass` or `code-review:fail` on the issue. +When the sequencer picks the issue up, it starts Juan Claude as an asynchronous agent on `feature//hydra-build`: -Juan doesn't write out his `fixes_applied[]` and `unfixed[]` as JSON himself — Hydra only bundles them into one round file after the next step (Clyde). See ["How verdicts are persisted"](#how-verdicts-are-persisted) below. +1. He reads his own brief first: `agents/juan-claude-van-damme/` plus the reviewer brief under `images/reviewer/`. That brief is the authority on what he checks and what he may fix. +2. His review is scoped to the PR diff (ADR-020), so every line he comments on is actually touched in this PR. +3. He runs the mechanical gates and walks the ADRs and gate skills (see part 3). +4. His brief allows **bounded, mechanical fixes** (ADR-021). Missing licence header? He may add it. A naming preference? Not his department. Unlike the build, the review stage has no push step of its own; committing a bounded fix is left to his brief. +5. He writes his answer to `hydra-verdict.json` in the repository root. A later sequencer tick collects that file. -## Pipeline 3: Security review (Clyde Barcode, Sonnet) +## Pipeline 3: Security review (Clyde Barcode) -Right after code review (whether `:pass` or `:fail`), the `hydra-sequencer` flow queues `security-review:queued`. Clyde Barcode runs on the PR state *after* Juan Claude's fixes: +`security-review:queued` is added only when code review **passed**. A failed code review stops there. Clyde then runs on the branch as code review left it: -- Same shape as code review, plus **Semgrep** and pattern matching on CWE classes (SQL injection, XSS, path traversal, hardcoded secrets, …). -- May also push bounded fixes in PR mode. -- Writes `security-review:pass` or `security-review:fail`. +- Same shape as code review, with a security brief (`agents/clyde-barcode/` and `images/security/`): CWE classes such as SQL injection, XSS, path traversal and hardcoded secrets, with Semgrep scanning in his brief's toolset. +- His brief also allows bounded fixes. +- His verdict goes into `hydra-verdict.json` too. -Why sequential and not parallel? Because Clyde reviews what Juan Claude left behind. Parallel would mean Clyde looks at pre-fix code — he would then find security issues that Juan Claude had already cleaned up and you'd have to reconcile verdicts. Sequential is simpler and clearer. +Why sequential and not parallel? Because Clyde reviews what Juan Claude left behind, and only work that passed code review is worth a security review. Parallel would mean Clyde looks at code that code review might still reject or change. ## How verdicts are persisted -After a complete review round (Juan + Clyde back to back), Hydra bundles both verdicts into **one** file on the feature branch: `openspec/changes//reviews/.json` in the target repo. Rounds number sequentially — `1.json` after the first pass, `2.json` after a `retry:queued` cycle, and so on (just `ls reviews/*.json | wc -l` plus one). That file holds the `fixes_applied[]` and `unfixed[]` from **both** reviewers combined — which is what Axel Pliér will read later. An aggregated status snapshot runs alongside in `openspec/changes//hydra.json` (see part 6 for the structure and how to read it). +Each review stage writes one file, **`hydra-verdict.json`**, in the root of its checkout: -## The applier: Axel Pliér's binary gate +```json +{"verdict": "pass", "checks_run": ["hydra-gates"], "checks_skipped": [], "findings": [], "fixes_applied": [], "unfixed": []} +``` + +The stage's filesystem is thrown away when it ends, so the flow collects that file by name. The sequencer then routes on it: + +- `verdict: pass` → `:pass`, and the next stage's `:queued` label is added. +- `verdict: fail` → `:fail`, and nothing further is queued. +- File missing or unreadable, `checks_run` without `hydra-gates`, or a stage that could not run → **`needs-input`**. This is the only place `needs-input` is set; the build lane never sets it. + +After a pass or fail the stage's `:queued` label is removed, the opposite verdict label is cleared, and lock and slot are released. After `needs-input` the stage's `:queued` label **stays**, so the sequencer picks the issue up again on every tick until someone removes that label. -With both reviews in, Hydra has three options: +Separately, the `hydra-record-stage` flow writes a stage record into `openspec/changes//hydra.json` after the label has moved, and refuses a stage result that carries no owner. Known gap: review stages are currently recorded under the stage name `build`. Part 6 shows how to read `hydra.json`. -1. **Both reviews `:pass` and Juan and Clyde together pushed 0 fixes.** - Nothing changed after the original build, so no new risks. **Axel is skipped.** Issue gets `done`. Saves tokens. +## The applier: Axel Pliér's binary gate + +`applier:queued` is added when security review passes. There is no rule that skips the applier; every issue that passes security review reaches Axel. He runs through the same generic stage step as the reviewers, reads his brief (`agents/axel-plier/` and `images/applier/`), and answers with one binary verdict in `hydra-verdict.json`: pass or fail. -2. **Both reviews `:pass` and ≥ 1 fix was pushed.** - The code has changed since the original build. Before we trust the final state, Hydra reruns the mechanical gates (PHPCS, Psalm, PHPStan, ...). If those pass, Axel Pliér is up. He reads: - - the final diff, - - the `fixes_applied[]` + `unfixed[]` from both reviews, - - all inline comments on the PR. - - And gives one binary answer: `{pass: true}` or `{pass: false, blocking: [...]}`. He has no Write or Edit tools. He judges, he doesn't intervene. +His brief gives him **no fix authority**: he judges, he doesn't intervene. (In the retired container pipeline his container had no Write or Edit tools at all; in the flow the stage step is shared, so "no fixes" is his brief's rule.) -3. **One of the reviews `:fail`.** - The reviewers are the authority. Axel is skipped. Issue goes to `needs-input`. A human decides whether this warrants a `retry:queued` or `rebuild:queued` (see part 6). +`applier:pass` is the end of the lane. Nothing merges the PR and no `done` label is set: a human reviews the PR and merges it. ## Labels: one source of truth -Hydra is **stateless**. The whole state machine lives on the issue labels on GitHub. The `hydra-dispatch` flow polls the forge every five minutes, and the `hydra-sequencer` flow reads an issue's labels to decide what to do next. If a stage crashes, the `hydra-lock-reaper` flow frees its lock, and the next pass reads the labels again and picks up where it left off. +The pipeline's state lives in the **issue labels** on GitHub, plus a per-issue lock while a stage is in flight. There are no `:running` labels: "in progress" is the lock, not a label. - Two flows in hydra's flows/ folder share the work, run by OpenRegister. hydra-dispatch runs on a schedule: it polls the forge for queued work and starts at most a fixed number of pipelines at once (POOL_SIZE). hydra-sequencer carries one issue: it takes that issue's lock, runs the dependency and sibling checks, reads the current label to decide the stage, has the hermiq ExApp run that stage, and advances the label through hydra-label-transition. Every stage's result is written to hydra.json by hydra-record-stage before the label moves. Until 2026 this was two shell scripts, hydra-supervisor.sh (the pool manager) and orchestrate.sh (the per-stage runner). Both are retired. Part 7 shows how the flows run unattended. + Both live in hydra's flows/ folder and run in OpenRegister, with the stages executed by the hermiq ExApp. hydra-sequencer is the pipeline. It runs every five minutes, searches the whole organisation for queued issues, claims a slot and a per-issue lock, runs the stage, and writes the labels itself. hydra-dispatch does not search the forge and does not hand work to the sequencer: given an issue reference, it claims a slot (capacity 10) and runs one hermiq agent step. Both ship with enabled: false, so they run only on an instance where someone switched them on. The retired shell supervisor and orchestrate.sh did this work until 2026. Part 7 shows how the flows run unattended. -Per stage there are four possible states: `:queued`, `:running`, `:pass`, `:fail`. They always live **on the issue, never on the PR.** That's a deliberate choice: the issue is the unit of work, the PR is an artefact of a build cycle. +**One tick, one unit of work.** Each sequencer run does at most one thing, in this priority order: -Alongside the stage labels there are metadata labels that live next to them: +1. Collect a review stage that is already in flight. +2. Otherwise the first open issue labelled `applier:queued`, +3. then `security-review:queued`, +4. then `code-review:queued`, +5. then `build:queued`. -| Label | Meaning | -|---|---| -| `openspec` | Issue follows the OpenSpec working model | -| `yolo` | May auto-merge as long as all gates are green — no human approve needed | -| `agent-maxed-out` | Persona has used up its turn budget; output may be incomplete | -| `needs-input` | Hydra has stopped the pipeline, human's turn | +Each lookup is a GitHub search, `org:ConductionNL is:issue is:open label:"
@@ -175,11 +179,11 @@ Mechanical-only misses context-dependent errors; AI-only is expensive, not predi
-**2. What does ADR-020 say about gate scope, and when do you switch that off via `HYDRA_REVIEW_SCOPE=full`?** +**2. What does ADR-020 say about gate scope, and where does `HYDRA_REVIEW_SCOPE=full` have an effect?**
-Think about what happens when you turn `phpcs` loose on a repo with lots of old technical debt. And when do you actually want that? +Think about what happens when you turn a gate loose on a repo with lots of old technical debt. And do the flows read that variable?
@@ -187,9 +191,9 @@ Think about what happens when you turn `phpcs` loose on a repo with lots of old ADR-020 says: **gates run on the PR diff, not on the whole repo**. -Reason: many repos drag along technical debt. `phpcs` across all of it produces hundreds of findings that have nothing to do with the current PR — every PR would be red. By only checking the touched lines, the gate only fires on what the builder just added or changed. +Reason: many repos drag along technical debt. A gate across all of it produces hundreds of findings that have nothing to do with the current PR — every PR would be red. By checking only the diff, the gate fires on what the build just added or changed. -**`HYDRA_REVIEW_SCOPE=full`** in `secrets/.env` disables this — gates then run on the whole repo. Use for: +The flows hard-code `--scope-to-diff`. **`HYDRA_REVIEW_SCOPE=full`** only changes local and manual runs (`dev-run.sh`, `manual-review.sh`), where the review then spans the whole repo. Use it for: - **Onboarding a new repo** into Hydra. - A **dedicated tech-debt sweep**. @@ -213,13 +217,13 @@ Three signals together point at a false positive. And the "tempting but wrong" r **Recognition** — three signals together: -1. The gate fires on the **same line** in every retry, while that line looks innocuous. -2. **Running the gate locally by hand** confirms: the pattern matches, but not for the intended reason (e.g. `grep 'dd('` also matches `$builder->add(`). +1. The gate fires on the **same line** on every build, while that line looks innocuous. +2. **Running the gate locally** confirms: the pattern matches, but not for the intended reason (e.g. a text search that also matches a comment or a string literal). 3. Nobody on the team can explain why this specific line should be complained about. -**Right fix:** tighten the **gate detection** in `scripts/run-quality.sh` or the associated skill. Example: `grep 'dd('` → `grep -wE '(^|[^A-Za-z0-9_])dd\('`. +**Right fix:** repair the **gate detection** in the `conduction/hydra-gates` package (`ConductionNL/.github`, `hydra-gates/`). Hydra's `scripts/run-hydra-gates.sh` only delegates to it. -**NOT:** another `retry:queued`. That reproduces the same false positive and wastes cycles. +**NOT:** re-queueing the build. That reproduces the same false positive and wastes a run. @@ -227,19 +231,18 @@ Three signals together point at a false positive. And the "tempting but wrong" r
-**4. Why does Hydra rerun the mechanical gates AFTER the reviewer fixes (the "quality recheck" phase)?** +**4. Where does Hydra re-run the gates automatically, and where does it NOT?**
-The reviewers may fix within scope. What can go wrong while they do that? +Think about the build lane's fix pass, and about what happens after a reviewer's bounded fix.
-Reviewers (Juan + Clyde) may push mechanical fixes within scope (ADR-021). While doing so a reviewer can **accidentally introduce a new violation** — for instance a PHPCS-style fix that breaks a rule elsewhere. - -The **quality recheck** reruns all mechanical gates on the final PR state to make sure what's there now is also objectively clean. If the recheck goes red → `needs-input`, no retry. The reviewer has stopped, the builder has stopped, a human looks. It's the last deterministic gate before a human decision falls. +- **Yes:** in the build lane. When the first gate run fails, there is one fix pass, and then the gates run once more over the fixed tree. That result decides `build:pass` or `build:fail`. +- **No:** after review. There is no post-review "quality recheck" in the flows. Instead each review stage must run the gates itself and list `hydra-gates` in `checks_run`; a verdict that doesn't is not accepted and the issue lands on `needs-input`.
@@ -254,7 +257,7 @@ The **quality recheck** reruns all mechanical gates on the final PR state to mak Wat Hydra is in één zin, en waarom we het hebben gebouwd. - De vier container-personas in grote lijnen: Al Gorithm, Juan Claude, Clyde, Axel. + De vier personas in grote lijnen: Al Gorithm, Juan Claude, Clyde, Axel. Wanneer je Hydra wél inzet, en wanneer juist niet. @@ -53,48 +53,64 @@ Hydra is Conductions interne **agentic CI/CD-platform**: een fabriek die OpenSpe Voor we erin duiken: een korte oriëntatie die elke volgende module makkelijker leesbaar maakt. Twee Conduction-repo's komen continu terug in deze leerlijn, en ze hebben **gescheiden taken**: -- **[`ConductionNL/hydra`](https://github.com/ConductionNL/hydra)** *(privé)* — de **fabriek**. Container images, agent-personas (Al Gorithm, Juan Claude, Clyde, Axel), orchestration-scripts, de `.claude/skills/`-catalogus (`hydra-gate-*`, `opsx-*`, `test-*`), OpenSpec changes. Alles wat *draait*. Clone alleen als je Hydra lokaal wilt draaien of een skill wilt aanpassen. -- **[`ConductionNL/.github`](https://github.com/ConductionNL/.github)** *(publiek)* — de **handleiding**. Developer-guides, way-of-work, ISO-compliance, de publieke Hydra-1-pager die je (optioneel) als warming-up leest, en gedeelde tooling zoals de globale Claude-settings. Alles wat je *eenmalig leest of installeert*. +- **[`ConductionNL/hydra`](https://github.com/ConductionNL/hydra)** *(privé)* — de **fabriek**. De pipeline-flows (`flows/`), de agent-personas (Al Gorithm, Juan Claude, Clyde, Axel), de overgebleven operator-scripts, de `.claude/skills/`-catalogus (`hydra-gate-*`, `opsx-*`, `test-*`), OpenSpec-changes. Alles wat *draait*. Clone alleen als je Hydra lokaal wilt draaien of een skill wilt aanpassen. +- **[`ConductionNL/.github`](https://github.com/ConductionNL/.github)** *(publiek)* — de **handleiding**. Developer-guides, way-of-work, ISO-compliance, de publieke Hydra-1-pager die je (optioneel) als warming-up leest, en gedeelde tooling: de globale Claude-settings en het pakket `conduction/hydra-gates` waarin de gate-logica zit (deel 3). Alles wat je *leest of installeert*. -**Hydra is de fabriek, `.github` is de handleiding.** Twee repo's, twee doeleinden, geen overlap. De volledige uitsplitsing staat in [`.github/docs/hydra/README.md`](https://github.com/ConductionNL/.github/blob/main/docs/hydra/README.md#hydra-repo-vs-github-repo). +**Hydra is de fabriek, `.github` is de handleiding.** Twee repo's, twee doeleinden. De volledige uitsplitsing staat in [`.github/docs/hydra/README.md`](https://github.com/ConductionNL/.github/blob/main/docs/hydra/README.md#hydra-repo-vs-github-repo). ## In één zin -Hydra is een **stateless, label-gedreven pipeline** die OpenSpec-voorstellen verandert in feature branches met code, tests, een review-history en een PR — klaar voor menselijke goedkeuring. +Hydra is een **label-gedreven pipeline** die een OpenSpec-change omzet in een feature branch met code en een PR waarin de gate-uitslag staat — klaar voor menselijke review en goedkeuring. -Geen "AI assistent die de developer helpt", maar een **fabriek**. Je gooit er een OpenSpec-change in en aan de andere kant rolt er een PR uit waar twee onafhankelijke reviewers naar gekeken hebben. +Geen "AI-assistent die de developer helpt", maar een **fabriek**. Je stopt er een OpenSpec-change in en aan de andere kant komt er een PR uit. Die merget Hydra nooit zelf. -Concreet: Hydra werkt op het `openspec/changes//` mapje — `proposal.md`, `design.md`, `tasks.md` en de spec-delta. Als die termen nieuw zijn, doe eerst de OpenSpec-leerlijn (zie tip bovenaan). +Concreet: Hydra werkt op het `openspec/changes//`-mapje — `proposal.md`, `design.md`, `tasks.md` en de spec-delta. Als die termen nieuw zijn, doe eerst de OpenSpec-leerlijn (zie tip bovenaan). + + + Je bereidt een OpenSpec-change voor (met de hand of met /opsx-ff) en commit die op de development-branch van de repo. In de body van het tracking-issue noem je de change (openspec/changes/<slug>), en je geeft dat issue het label build:queued. Dat label is de trigger. De hydra-sequencer-flow vindt het issue, leest de change-naam uit de issue-body, controleert of de change-map bestaat, en laat een agent de change implementeren op basis van proposal.md, tasks.md, design.md en specs/. Noemt het issue geen change, of bestaat de genoemde change niet, dan krijgt het build:blocked in plaats van een build. + + +## Hoe een change door Hydra reist + +Een kort overzicht; deel 2 loopt elke stap langs. + +1. **Build.** Een agent implementeert de change in een checkout van de basisbranch. De pipeline, niet de agent, commit en pusht het resultaat naar `feature//hydra-build`. +2. **Gates.** De pipeline draait de `hydra-gates`-suite over de diff. Faalt er een gate, dan volgt **één** fix-ronde: de agent krijgt de gate-output en repareert alleen wat daarin genoemd wordt, waarna de gates nog één keer draaien. +3. **PR.** Hydra opent een PR, of de gates nu groen eindigden (`build:pass`) of rood (`build:fail`). De gate-output staat in de PR-body. +4. **Review, als je erom vraagt.** Niets zet automatisch een review in de wachtrij. Zodra iemand het issue het label `code-review:queued` geeft, lopen er drie stages na elkaar, elk alleen als de vorige slaagde: code review, security review, applier. +5. **Een mens merget.** Geen enkele flow merget een PR, en geen enkele flow zet een `done`-label. ## Waarom bestaat Hydra? Conduction bouwt veel open-source apps voor de Nextcloud-werkplek. Het ritme van die fabriek wordt bepaald door twee dingen: hoe snel we **changes** kunnen schrijven, en hoe snel die changes **code** worden. Hydra automatiseert die tweede stap, met drie expliciete doelen: -1. **Doorlooptijd verkorten.** Een gemiddeld OpenSpec-voorstel wordt zonder Hydra in een halve dag tot drie dagen geïmplementeerd. Met Hydra is dat een halfuur tot een paar uur, afhankelijk van complexiteit. -2. **Kwaliteit borgen.** Geen enkele PR komt het systeem uit zonder mechanische quality gates (PHPCS, Psalm, PHPStan, ESLint, PHPUnit), een onafhankelijke code review en een onafhankelijke security review. Twee paar AI-ogen vóór er een mens kijkt. -3. **4-eyes principle behouden.** Hydra **merge-t niet zelf** — behalve wanneer een issue expliciet de `yolo`-vlag heeft, en zelfs dan moeten alle gates groen zijn. Een mens drukt op de knop. +1. **Doorlooptijd verkorten.** De stap van een uitgeschreven change naar een eerste reviewbare PR doet een agent, niet een mens. +2. **Kwaliteit borgen.** Elke build draait de mechanische `hydra-gates`-suite voordat de PR opengaat, en de PR laat de uitslag zien. Daarbovenop zitten drie onafhankelijke AI-reviewstages die je in de wachtrij kunt zetten. +3. **Vier-ogenprincipe behouden.** Hydra **merget niet**. Er bestaat geen auto-merge-pad: een mens leest de PR en drukt op de knop. ## De vier personas -Hydra is intern opgebouwd uit vier ephemere container-personas. Elk speelt één rol, in één container, met scoped credentials en strakke turn-budgets. Je hoeft hun namen nu niet uit het hoofd te kennen — ze komen terug in deel 2 — maar het helpt om vroeg het rollenpatroon te zien: +Hydra kent vier personas. Elk speelt één rol en heeft een eigen brief onder `agents//`. Je hoeft hun namen nu niet uit het hoofd te kennen — ze komen terug in deel 2 — maar het helpt om het rollenpatroon vroeg te zien: - - Bouwt code uit de change. Ziet nooit reviews terug. Stopt na een vast turn-budget. + + Bouwt de code uit de change. Ziet nooit reviews. Heeft geen shell en geen forge-credential: hij bewerkt bestanden, en de pipeline commit en pusht ze. - - Leest alleen de PR-diff. Kan zelf mechanische fixes doorvoeren binnen scope. + + Reviewt de PR-diff. Zijn brief staat kleine, mechanische fixes binnen scope toe. - - Loopt na Juan Claude. Zelfde shape, plus Semgrep en patroon-matching op CWE-klassen. + + Loopt na Juan Claude, en alleen als de code review slaagde. Zelfde opzet, met een security-brief: CWE-klassen zoals injectie, XSS, path traversal en hardcoded secrets. - - Beslist binair: gaat dit door of niet? Leest beide reviews en de uiteindelijke code. Géén fix-tools. + + Beslist binair: gaat dit door of niet? Loopt na een geslaagde security review. Zijn brief geeft hem geen bevoegdheid om te fixen. -Belangrijk: **Al Gorithm draait op Haiku, de andere drie op Sonnet** (met Opus als fallback). Reden: bouwen volgt patronen en is goedkoop te doen met Haiku; oordelen vraagt meer diepgang. Op Claude Max heb je een aparte Sonnet-only en all-models quota, dus deze splitsing rekt het budget op. +Over modellen: de `agents//config.yaml` van elke persona noemt **Sonnet**. De flows zelf geven geen model mee, dus een stage draait op het model waarmee de hermiq-runner die hem uitvoert is ingesteld. Omgevingsvariabelen als `HYDRA_BUILDER_MODEL` sturen de flows niet; `HYDRA_REVIEWER_MODEL` wordt alleen gelezen door het handmatige `scripts/manual-review.sh`. + +> **Historische noot:** een eerder ontwerp liet de builder op **Haiku** draaien — bouwen leek goedkoop patroonwerk. In de praktijk werd Haiku betrapt op *het overslaan van quality gates*, ondanks expliciete instructies, en is de config van de builder naar Sonnet gezet. Lees je oudere docs waarin "builder = Haiku" staat, dan weet je waarom die verouderd zijn. ## Wanneer wel, wanneer niet @@ -104,27 +120,27 @@ Hydra is geen wondermiddel. De vuistregel: - Het werk past binnen één coherente OpenSpec-change (één feature, niet een refactor van het hele systeem). - De acceptatiecriteria mechanisch te toetsen zijn (er bestaan tests of die zijn met PHPUnit/Newman/Playwright snel te schrijven). -- De doel-repo is **gereed** voor Hydra: heeft `hydra-gate-*` labels, een `app-config.json`, en draait de standaard quality-suite. +- De doel-repo **gereed** is voor Hydra: hij heeft de stage-labels (`scripts/create-labels.sh` maakt ze aan), de change staat gecommit op de `development`-branch, en de `hydra-gates`-suite geeft er een zinvolle uitslag op. **Niet inzetten als:** - Het gaat om een eenmalige spike, prototype, of "even iets uitproberen" zonder reviewer-discipline. - De architectuur wijzigt op een manier die niet in één PR past (multi-repo refactor, breaking schema-change met datamigratie). -- Je geen vertrouwen hebt in de gates op de doel-repo — Hydra is alleen zo veilig als zijn quality checks. +- Je geen vertrouwen hebt in de gates op de doel-repo — Hydra is maar zo veilig als zijn quality checks. - Het werk vereist menselijke onderhandeling met een externe partij (klant, leverancier) voordat het kan beginnen. ## Wat Hydra **niet** doet Een veelgemaakte vergissing: denken dat Hydra je hele product bedenkt. Het tegendeel is waar. -- **Hydra schrijft geen changes (of specs).** Die worden vooraf aangeleverd, of een mens schrijft ze handmatig (met `/opsx-ff`). +- **Hydra schrijft geen changes (of specs).** Die worden vooraf aangeleverd, of een mens schrijft ze met de hand (met `/opsx-ff`). - **Hydra doet niet aan visie of strategie.** Welke features we bouwen is een mensenkeuze. - **Hydra besluit niet over architectuur.** Daarvoor zijn ADR's, beheerd door mensen (zie `hydra/openspec/architecture/`). -- **Hydra merge-t niet eigenmachtig.** Behalve `yolo`-issues — en daar zijn alle gates nog steeds harde voorwaarden. +- **Hydra merget niet.** Niet na een groene build, niet na drie geslaagde reviews, en ook niet bij een issue met het label `yolo`: geen enkele flow leest dat label. ## Test jezelf -Vier korte vragen om te checken of je dit deel begrepen hebt. Vastgelopen? Klik **Hint**. Curieus naar het antwoord? Klik **Antwoord**. +Vier korte vragen om te checken of je dit deel begrepen hebt. Vastgelopen? Klik **Hint**. Benieuwd naar het antwoord? Klik **Antwoord**.
@@ -132,17 +148,17 @@ Vier korte vragen om te checken of je dit deel begrepen hebt. Vastgelopen? Klik
-Hydra doet één specifieke stap in onze fabriek. Welke stap, en welke twee voordelen levert dat op? +Hydra doet één specifieke stap in onze fabriek. Welke stap, en wat levert die aan het eind op?
-Hydra automatiseert de stap van **OpenSpec-change naar gereviewde feature-branch + PR**. Drie expliciete doelen: +Hydra automatiseert de stap van **OpenSpec-change naar feature branch + PR**. Drie expliciete doelen: -1. **Doorlooptijd verkorten** — van een halve dag tot drie dagen handmatig naar een halfuur tot een paar uur. -2. **Kwaliteit borgen** — verplichte mechanische gates (PHPCS, Psalm, PHPStan, ESLint, PHPUnit) + twee onafhankelijke AI-reviews. -3. **4-eyes-principe behouden** — Hydra merge-t niet zelf; een mens drukt op de knop. Uitzondering: `yolo`-issues, en zelfs dan moeten alle gates groen zijn. +1. **Doorlooptijd verkorten** — een agent doet het implementatiewerk, niet een mens. +2. **Kwaliteit borgen** — elke build draait de mechanische `hydra-gates`-suite en de PR laat de uitslag zien; daarbovenop kun je drie AI-reviewstages in de wachtrij zetten. +3. **Vier-ogenprincipe behouden** — Hydra merget nooit; een mens drukt op de knop. Voor `yolo` bestaat geen uitzondering.
@@ -150,22 +166,22 @@ Hydra automatiseert de stap van **OpenSpec-change naar gereviewde feature-branch
-**2. Waarom kiest Hydra voor mechanische quality gates (PHPCS, Psalm, PHPStan, ESLint, PHPUnit) in plaats van alleen AI-review?** +**2. Waarom kiest Hydra voor mechanische quality gates in plaats van alleen AI-review?**
-AI-reviewers kunnen overtuigend klinken zonder feitelijk correct te zijn. Mechanische checks hebben één eigenschap die AI-review per definitie mist. +AI-reviewers kunnen overtuigend klinken zonder feitelijk gelijk te hebben. Mechanische checks hebben één eigenschap die AI-review per definitie mist.
-Mechanische gates zijn **deterministisch en niet te overtuigen**. PHPStan kun je niet ompraten; ESLint trekt zich niets aan van een goed onderbouwd verhaal. Twee gevolgen: +Mechanische gates zijn **deterministisch en niet om te praten**. Dezelfde diff geeft dezelfde uitslag, en een goed onderbouwd verhaal verandert daar niets aan. Twee gevolgen: -1. **Vals-positieven en hallucinaties worden gefilterd** vóórdat een reviewer ernaar kijkt — een PR die de gates niet haalt komt niet eens bij Juan Claude of Clyde langs. -2. **Standaarden zijn afdwingbaar over de hele fleet** — codestijl, types en testresultaten zijn objectief, niet afhankelijk van wie er die dag reviewed. +1. **Elke PR heeft een objectieve uitslag** — de gate-output staat in de PR-body, en een build waarvan de gates rood blijven krijgt `build:fail`. Een mens ziet dat dus voordat hij besluit een review in de wachtrij te zetten. +2. **Standaarden zijn afdwingbaar over de hele fleet** — op elke repo draaien dezelfde gates, wie er die dag ook reviewt. Een reviewstage die niet kan aantonen dat hij de gates draaide, telt niet als pass. -AI-review komt erbovenop, niet in plaats ervan. Beide lagen vangen verschillende klassen van problemen. +AI-review komt erbovenop, niet in plaats ervan. Beide lagen vangen verschillende soorten problemen.
@@ -173,11 +189,11 @@ AI-review komt erbovenop, niet in plaats ervan. Beide lagen vangen verschillende
-**3. Welke vier personas heeft Hydra, en welke is degene die NIET kan schrijven?** +**3. Welke vier personas heeft Hydra, en welke mag GEEN code wijzigen?**
-Eén persona is bewust read-only — zijn rol is een binaire gate ("gaat dit door of niet?"). Hij heeft geen Write- of Edit-tools. +De rol van één persona is een binaire gate ("gaat dit door of niet?"). Zijn brief geeft hem geen bevoegdheid om te fixen.
@@ -185,10 +201,10 @@ Eén persona is bewust read-only — zijn rol is een binaire gate ("gaat dit doo De vier personas: -- **Al Gorithm** — builder (Haiku). Schrijft code. -- **Juan Claude van Damme** — code reviewer (Sonnet). Mag bounded fixes pushen. -- **Clyde Barcode** — security reviewer (Sonnet). Mag ook bounded fixes pushen. -- **Axel Pliér** — applier (Sonnet, **géén schrijfrechten**). Leest de uiteindelijke diff + beide reviews en geeft één binair antwoord: `pass: true` of `pass: false`. Hij oordeelt, hij grijpt niet in. +- **Al Gorithm** — builder. Schrijft code; de pipeline commit en pusht die. +- **Juan Claude van Damme** — code reviewer. Zijn brief staat begrensde, mechanische fixes toe. +- **Clyde Barcode** — security reviewer. Hetzelfde, met een security-brief. +- **Axel Pliér** — applier. Geeft één binair antwoord: pass of fail. Hij oordeelt, hij grijpt niet in. @@ -196,20 +212,20 @@ De vier personas:
-**4. Noem twee situaties waarin je Hydra juist NIET inzet en handmatig werkt.** +**4. Noem twee situaties waarin je Hydra juist NIET inzet en met de hand werkt.**
-Denk aan: past het werk in één change, is de doel-repo klaar, en is er iets niet-technisch dat eerst opgelost moet worden. +Denk aan: past het werk in één change, is de doel-repo klaar, en is er iets niet-technisch dat eerst opgelost moet worden?
-Een paar veelgenoemde: +Een paar veelvoorkomende: - **Het werk past niet in één OpenSpec-change** — multi-repo refactor, breaking schema-change met datamigratie. -- **De doel-repo is niet gereed** — geen `hydra-gate-*` labels, geen `app-config.json`, of een onbetrouwbare quality-suite. Hydra is alleen zo veilig als zijn gates. +- **De doel-repo is niet gereed** — geen stage-labels, geen change gecommit op `development`, of gates die je op die repo niet vertrouwt. Hydra is maar zo veilig als zijn gates. - **Spike of prototype zonder reviewer-discipline** — "even iets uitproberen". De overhead weegt niet op tegen de winst. - **Menselijke onderhandeling vereist** — een klant, leverancier of juridisch punt moet eerst opgelost worden voor het werk kan beginnen. @@ -221,7 +237,7 @@ Een paar veelgenoemde: - De drie pipelines (build, code-review, security-review) en hoe ze sequentieel op elkaar overdragen. - Het complete label-state-machine: build:queued → ... → done / needs-input. - Wat de applier (Axel Pliér) doet, en waarom hij overgeslagen kan worden. - Het verschil tussen retry:queued en rebuild:queued. + De build-lane en zijn vier uitkomsten: build:pass, build:fail, build:no-change, build:blocked. + De review-lane (code review → security review → applier) en hoe elk verdict wordt doorgestuurd. + Hoe één geplande flow, hydra-sequencer, per tick één stuk werk oppakt. + Op welke labels geen enkele flow reageert, waaronder retry:queued, rebuild:queued en yolo. @@ -33,133 +33,172 @@ In deel 1 zag je dat Hydra vier personas heeft. In dit deel kijken we naar de ** Een idee van wat labels op een GitHub-issue zijn en hoe je die toevoegt. -## Drie pipelines, één pijplijn +## Drie pipelines, twee lanes -Hydra heeft technisch gezien drie pipelines: **build**, **code-review** en **security-review**. Maar in praktijk werken ze altijd in dezelfde volgorde, automatisch aangestuurd door labels. Onder de motorkap is dat één state-machine: +Hydra heeft drie pipelines: **build**, **code review** en **security review**, met de applier als derde reviewstage na security. Ze worden allemaal aangestuurd door labels op het tracking-issue, en ze vallen uiteen in twee lanes. De build-lane start zodra je een issue het label `build:queued` geeft. De review-lane start pas als iemand het label `code-review:queued` zet — **dat doet niets voor je**, ook een groene build niet. ``` -ready-to-build (of -ready-to-build op een dev-werkstation — zie deel 5) +build:queued ← zet jij + ↓ build → gates → (één fix-ronde → gates) +build:pass PR geopend, gates groen +build:fail PR geopend, gates nog rood +build:no-change de agent veranderde niets, niets gepusht +build:blocked geen change genoemd, change niet gevonden, of de build kon niet afronden + +code-review:queued ← zet jij ↓ -build:queued → build:running → build:pass - ↓ -code-review:queued → :running → :pass / :fail - ↓ (altijd doorlopen — ook bij :fail) -security-review:queued → :running → :pass / :fail - ↓ -beslissing: - beide reviews :pass + 0 fixes → done (Axel overgeslagen) - beide reviews :pass + ≥1 fix → applier:queued → :running → :pass / :fail - één van beide reviews :fail → needs-input (Axel overgeslagen, mens beslist) +code-review:pass → security-review:queued +code-review:fail → stop +security-review:pass → applier:queued +security-review:fail → stop +applier:pass → einde: een mens reviewt en merget de PR +applier:fail → stop + +needs-input ← een reviewstage zonder leesbaar verdict, een verdict dat + niet laat zien dat de gates draaiden, of een stage die + niet kon draaien ``` -De pijplijn heeft één belangrijke regel: **iedere uitkomst na review is terminaal**. Hydra fix-t niet automatisch in een loop. Slaagt het in één run, mooi. Slaagt het niet, dan komt het issue op `needs-input` en moet een mens beslissen wat de volgende stap is. Daarover meer in deel 6. +Voor Hydra is elke stop definitief: er is geen automatische retry en geen fix-loop na review. Een mens leest de PR en de labels en beslist wat er daarna gebeurt. Daar gaat deel 6 over. -## Pipeline 1: Build (Al Gorithm, Haiku) +## Pipeline 1: Build (Al Gorithm) -De builder krijgt de change, een schone clone van de doel-repo, en een turn-budget. Geen review-history, geen feedback van eerdere runs. Hij implementeert de tasks uit `tasks.md`, draait de quality-suite, opent een draft-PR. +De `hydra-sequencer`-flow pakt een issue met `build:queued` op en loopt deze stappen door: -Volgorde binnen de build-fase: +1. **Change vinden.** De change-naam wordt uit de **body** van het issue gelezen: de eerste `openspec/changes/` die erin staat. Geen change genoemd → `build:blocked`. +2. **Basisbranch kiezen.** `development` als de repo die heeft, anders de standaardbranch van de repo. De change-map moet op die branch bestaan, anders → `build:blocked`. +3. **Capaciteit claimen.** Een slot uit de gedeelde pool en een lock per issue (zie het labels-hoofdstuk verderop). +4. **Bouwen.** Een agent implementeert de change in een checkout van de basisbranch en leest daarvoor `proposal.md`, `tasks.md`, `design.md` en `specs/`. Hij kan bestanden lezen en bewerken, maar heeft geen shell en mag geen git draaien. De **pipeline** commit en pusht zijn werk naar `feature//hydra-build`, beperkt tot `lib/`, `src/`, `tests/`, `openspec/`, `appinfo/`, `openapi.json`, `CHANGELOG.md` en `README.md`. Heeft de agent niets veranderd → `build:no-change`. Kon de build niet afronden (bijvoorbeeld omdat hij een bestand buiten die lijst aanraakte en de push geweigerd werd) → `build:blocked`. +5. **Gates.** De pipeline draait `scripts/run-hydra-gates.sh --scope-to-diff --base origin/`. Exitcode 0 betekent nul failures → `build:pass`. +6. **Eén fix-ronde.** Anders krijgt de agent de gate-output en repareert hij alleen wat de gates noemden. Verandert hij niets → `build:fail`. Verandert hij wel iets, dan draaien de gates opnieuw: 0 → `build:pass`, al het andere → `build:fail`. +7. **PR.** Bij zowel `build:pass` als `build:fail` opent Hydra een PR van `feature//hydra-build` naar de basisbranch, met als titel `: built by hydra for #` en in de body `Closes #` plus de gate-output. Die PR wordt niet gemerged. +8. **Opruimen.** `build:queued` gaat eraf, lock en slot worden vrijgegeven, en de stage wordt vastgelegd in `hydra.json`. -1. **Implementatie** — leest `proposal.md`, `design.md` en `tasks.md` uit `openspec/changes//` in de doel-repo, schrijft code. -2. **Quality checks** — PHPCS, PHPMD, Psalm, PHPStan, ESLint, Stylelint, `composer audit`, `npm audit`. -3. **PHPUnit + Newman** — backend en API-tests. -4. **Browser-tests** — Playwright MCP loopt door de UI heen. -5. **`fix-quality` / `fix-browser`** — als checks rood worden, krijgt Al Gorithm één tot twee pogingen om mechanisch te repareren. Dit is een *pre-review* fixup, geen review-loop. -6. **Verdict** — `build:pass` (PR is opengezet) of `build:fail` (kapotte build, `needs-input`). +De flow draait alleen de `hydra-gates`-suite. PHPUnit, Psalm, PHPStan, Newman en browsertests horen **niet** bij de build-lane; `scripts/run-quality.sh` en `scripts/run-browser-tests.sh` bestaan nog wel, maar geen enkele flow roept ze aan. Wat de gates wél controleren, lees je in deel 3. -Belangrijk: Al Gorithm draait op **Haiku**. Reden uit deel 1: hij volgt patronen, hij oordeelt niet. Door hem op Haiku te zetten houden we Sonnet-budget vrij voor de reviewers. +`build:fail` leidt **niet** tot `needs-input`. De PR staat er, met de rode gate-output in de body, en het is aan een mens om die te lezen. - Geen reviews, geen verdict-history, geen labels behalve het eigen build:*. Hij weet dus niet dat een eerdere reviewer iets afkeurde — die feedback bereikt hem alleen via een expliciete retry:queued-cyclus (zie onder). + Geen reviews, geen verdict-historie, geen labels. De build-prompt bevat de change en het issue; de fix-ronde alleen de gate-output. Bevindingen van reviewers komen nooit bij de builder terecht: geen enkele flow speelt ze terug (zie retry en rebuild verderop). -## Pipeline 2: Code review (Juan Claude van Damme, Sonnet) +## Pipeline 2: Code review (Juan Claude van Damme) + +Niets brengt een issue van `build:pass` naar review. Dat besluit jij, door `code-review:queued` te zetten — bijvoorbeeld met het operator-script, dat ook de kaart op het projectbord meeneemt: + +```bash +scripts/hydra-label.sh ConductionNL/ add code-review:queued +``` + +De flows lezen alleen kale labelnamen, dus draai het zonder `HYDRA_LABEL_PREFIX`. + +Als de sequencer het issue oppakt, start hij Juan Claude als asynchrone agent op `feature//hydra-build`: + +1. Hij leest eerst zijn eigen brief: `agents/juan-claude-van-damme/` plus de reviewer-brief onder `images/reviewer/`. Die brief bepaalt wat hij controleert en wat hij mag fixen. +2. Zijn review beperkt zich tot de PR-diff (ADR-020), zodat elke regel waar hij iets over zegt ook echt in deze PR is aangeraakt. +3. Hij draait de mechanische gates en loopt de ADR's en gate-skills langs (zie deel 3). +4. Zijn brief staat **begrensde, mechanische fixes** toe (ADR-021). Ontbreekt een licentieheader? Die mag hij toevoegen. Een voorkeur voor een andere naam? Niet zijn afdeling. Anders dan de build heeft de reviewstage geen eigen push-stap; het committen van een begrensde fix laat de flow aan zijn brief over. +5. Hij schrijft zijn antwoord naar `hydra-verdict.json` in de root van de repository. Een latere tick van de sequencer haalt dat bestand op. -Zodra `build:pass` gezet wordt, queue-t de `hydra-sequencer`-flow automatisch `code-review:queued`. Juan Claude pakt het op: +## Pipeline 3: Security review (Clyde Barcode) -1. Leest **alleen de PR-diff** (`HYDRA_REVIEW_SCOPE=diff`, ADR-020). Zo blijft de scope behapbaar en wordt elke regel die hij commentaar geeft daadwerkelijk in deze PR aangeraakt. -2. Loopt de ADR-bibliotheek en gate-skills langs (zie deel 3). -3. Voor **mechanische, in-scope fouten** mag hij zelf fixen (ADR-021: bounded-fix scope). PHPCS-headers ontbreken? Hij voegt ze toe. Pijnlijke variabele-naam? Niet zijn pakkie-an. -4. Voor elk gevonden probleem schrijft hij een inline-comment op de PR met prefix `[fixed:...]` of `[unfixed:...]`. -5. Hij commit + pusht zijn fixes naar de feature branch en zet `code-review:pass` of `code-review:fail` op het issue. +`security-review:queued` wordt alleen toegevoegd als de code review **slaagde**. Een afgekeurde code review stopt daar. Clyde draait vervolgens op de branch zoals de code review die achterliet: -Juan's `fixes_applied[]` en `unfixed[]` schrijft hij niet zelf weg als JSON — Hydra bundelt ze pas na de volgende stap (Clyde) tot één rondebestand. Zie ["Hoe verdicts gepersisteerd worden"](#hoe-verdicts-gepersisteerd-worden) hieronder. +- Zelfde opzet als code review, met een security-brief (`agents/clyde-barcode/` en `images/security/`): CWE-klassen zoals SQL-injectie, XSS, path traversal en hardcoded secrets, met Semgrep-scans in de toolset van zijn brief. +- Ook zijn brief staat begrensde fixes toe. +- Ook zijn verdict komt in `hydra-verdict.json`. -## Pipeline 3: Security review (Clyde Barcode, Sonnet) +Waarom na elkaar en niet tegelijk? Omdat Clyde reviewt wat Juan Claude achterliet, en alleen werk dat door de code review kwam een security review waard is. Tegelijk draaien zou betekenen dat Clyde naar code kijkt die de code review nog kan afkeuren of aanpassen. -Direct na code-review (of die nu `:pass` of `:fail` was) queue-t de `hydra-sequencer`-flow `security-review:queued`. Clyde Barcode loopt op de PR-staat ná Juan Claude's fixes: +## Hoe verdicts worden vastgelegd -- Zelfde shape als code-review, plus **Semgrep** en patroon-matching op CWE-klassen (SQL-injectie, XSS, path-traversal, hardcoded secrets, …). -- Mag ook bounded fixes pushen in PR-mode. -- Schrijft `security-review:pass` of `security-review:fail`. +Elke reviewstage schrijft één bestand, **`hydra-verdict.json`**, in de root van zijn checkout: + +```json +{"verdict": "pass", "checks_run": ["hydra-gates"], "checks_skipped": [], "findings": [], "fixes_applied": [], "unfixed": []} +``` -Waarom sequentieel en niet parallel? Omdat Clyde reviewed wat Juan Claude heeft achtergelaten. Parallel zou betekenen dat Clyde naar pre-fix code kijkt — dan zou hij security-issues vinden die Juan Claude al weggepoetst had en moet je de verdicts gaan reconciliëren. Sequentieel is simpeler en duidelijker. +Het bestandssysteem van de stage wordt weggegooid zodra hij klaar is, dus de flow haalt dat bestand op naam op. Daarna stuurt de sequencer het issue door: -## Hoe verdicts gepersisteerd worden +- `verdict: pass` → `:pass`, en het `:queued`-label van de volgende stage wordt toegevoegd. +- `verdict: fail` → `:fail`, en er wordt niets meer in de wachtrij gezet. +- Bestand ontbreekt of is onleesbaar, `checks_run` noemt geen `hydra-gates`, of de stage kon niet draaien → **`needs-input`**. Dit is de enige plek waar `needs-input` wordt gezet; de build-lane zet het nooit. -Na een complete review-ronde (Juan + Clyde achter elkaar) bundelt Hydra beide verdicts in **één** bestand op de feature branch: `openspec/changes//reviews/.json` in de doel-repo. Rondes nummeren sequentieel — `1.json` na de eerste pass, `2.json` na een `retry:queued`-cyclus, enzovoort (gewoon `ls reviews/*.json | wc -l` plus één). Daarin zitten de `fixes_applied[]` én `unfixed[]` van **beide** reviewers samen — dat is wat Axel Pliér straks leest. Een geaggregeerde status-snapshot loopt parallel mee naar `openspec/changes//hydra.json` (zie deel 6 voor de structuur en hoe je hem uitleest). +Na een pass of fail gaat het `:queued`-label van de stage eraf, wordt het tegenovergestelde verdict-label gewist, en worden lock en slot vrijgegeven. Na `needs-input` **blijft** het `:queued`-label van de stage staan, zodat de sequencer het issue bij elke tick opnieuw oppakt tot iemand dat label weghaalt. -## De applier: Axel Pliér's binaire gate +Los daarvan schrijft de `hydra-record-stage`-flow een stage-record naar `openspec/changes//hydra.json`, nadat het label is verzet, en weigert een stage-resultaat zonder eigenaar. Bekend gat: reviewstages worden op dit moment vastgelegd onder de stage-naam `build`. In deel 6 zie je hoe je `hydra.json` leest. -Met beide reviews binnen heeft Hydra drie opties: +## De applier: Axel Pliérs binaire gate -1. **Beide reviews `:pass` én Juan en Clyde hebben samen 0 fixes gepusht.** - Niets veranderd ná de oorspronkelijke build, dus geen nieuwe risico's. **Axel wordt overgeslagen.** Issue krijgt `done`. Bespaart tokens. +`applier:queued` wordt toegevoegd als de security review slaagt. Er is geen regel die de applier overslaat; elk issue dat door de security review komt, komt bij Axel. Hij draait via dezelfde generieke stage-stap als de reviewers, leest zijn brief (`agents/axel-plier/` en `images/applier/`), en antwoordt met één binair verdict in `hydra-verdict.json`: pass of fail. -2. **Beide reviews `:pass` én er is ≥ 1 fix gepusht.** - De code is gewijzigd na de oorspronkelijke build. Voor we vertrouwen op de uiteindelijke staat draait Hydra opnieuw de mechanische gates (PHPCS, Psalm, PHPStan, ...). Slagen die, dan komt Axel Pliér aan zet. Hij leest: - - de uiteindelijke diff, - - de `fixes_applied[]` + `unfixed[]` uit beide reviews, - - alle inline-comments op de PR. - - En geeft één binair antwoord: `{pass: true}` of `{pass: false, blocking: [...]}`. Hij heeft géén Write- of Edit-tools. Hij oordeelt, hij grijpt niet in. +Zijn brief geeft hem **geen bevoegdheid om te fixen**: hij oordeelt, hij grijpt niet in. (In de uitgefaseerde container-pipeline had zijn container helemaal geen Write- of Edit-tools; in de flow is de stage-stap gedeeld, dus "niet fixen" is een regel uit zijn brief.) -3. **Eén van beide reviews `:fail`.** - De reviewers zijn de autoriteit. Axel wordt overgeslagen. Issue gaat naar `needs-input`. Een mens beslist of dit een `retry:queued` of `rebuild:queued` waard is (zie deel 6). +Met `applier:pass` is de lane klaar. Niets merget de PR en er wordt geen `done`-label gezet: een mens reviewt de PR en merget hem. ## Labels: één bron van waarheid -Hydra is **stateless**. Het hele state-machine staat op de issue-labels op GitHub. De `hydra-dispatch`-flow pollt de forge elke vijf minuten, en de `hydra-sequencer`-flow leest de labels van een issue om te beslissen wat er daarna gebeurt. Crasht een stage, dan geeft de `hydra-lock-reaper`-flow de lock vrij, en de volgende ronde leest de labels opnieuw en pakt op waar het ophield. +De toestand van de pipeline staat in de **issue-labels** op GitHub, plus een lock per issue zolang er een stage loopt. `:running`-labels bestaan niet: "bezig" is de lock, geen label. -Per stage zijn er vier mogelijke states: `:queued`, `:running`, `:pass`, `:fail`. Die staan **altijd op het issue, nooit op de PR.** Dat is een bewuste keuze: het issue is de eenheid van werk, de PR is een artefact van een buildcycle. + + Beide staan in de map flows/ van hydra en draaien in OpenRegister; de stages worden uitgevoerd door de hermiq-ExApp. hydra-sequencer is de pipeline. Hij draait elke vijf minuten, zoekt in de hele organisatie naar issues in de wachtrij, claimt een slot en een lock per issue, draait de stage, en zet de labels zelf. hydra-dispatch zoekt niet op de forge en geeft geen werk door aan de sequencer: met een issue-referentie claimt hij een slot (capaciteit 10) en draait hij één hermiq-agentstap. Beide worden geleverd met enabled: false, dus ze draaien alleen op een instantie waar iemand ze heeft aangezet. Tot 2026 deden de uitgefaseerde shell-supervisor en orchestrate.sh dit werk. Deel 7 laat zien hoe de flows zonder toezicht draaien. + -Naast de stage-labels zijn er metadata-labels die ernaast bestaan: +**Eén tick, één stuk werk.** Elke run van de sequencer doet hooguit één ding, in deze volgorde van voorrang: -| Label | Betekenis | -|---|---| -| `openspec` | Issue volgt het OpenSpec-werkmodel | -| `yolo` | Mag auto-mergen mits alle gates groen zijn — geen menselijke approve nodig | -| `agent-maxed-out` | Persona heeft zijn turn-budget opgemaakt; output is mogelijk incompleet | -| `needs-input` | Hydra heeft de pijplijn gestopt, mens is aan zet | +1. Een reviewstage ophalen die al loopt. +2. Anders het eerste open issue met `applier:queued`, +3. dan `security-review:queued`, +4. dan `code-review:queued`, +5. dan `build:queued`. + +Elke zoekvraag is een GitHub-search, `org:ConductionNL is:issue is:open label:"
@@ -148,11 +178,11 @@ Alleen-mechanisch mist context-afhankelijke fouten; alleen-AI is duur, niet voor
-**2. Wat zegt ADR-020 over de gate-scope, en wanneer schakel je dat via `HYDRA_REVIEW_SCOPE=full` uit?** +**2. Wat zegt ADR-020 over de gate-scope, en waar heeft `HYDRA_REVIEW_SCOPE=full` effect?**
-Denk aan wat er gebeurt als je `phpcs` op een repo met veel oude technische schuld loslaat. En wanneer wil je dat juist wél? +Denk aan wat er gebeurt als je een gate loslaat op een repo met veel oude technische schuld. En lezen de flows die variabele?
@@ -160,13 +190,13 @@ Denk aan wat er gebeurt als je `phpcs` op een repo met veel oude technische schu ADR-020 zegt: **gates draaien op de PR-diff, niet op de hele repo**. -Reden: veel repo's slepen technische schuld mee. `phpcs` over alles geeft honderden findings die niets met de huidige PR te maken hebben — elke PR zou rood worden. Door alleen de geraakte regels te checken slaat de gate alleen om op wat de builder net heeft toegevoegd of gewijzigd. +Reden: veel repo's slepen technische schuld mee. Een gate over alles geeft honderden bevindingen die niets met de huidige PR te maken hebben — elke PR zou rood worden. Door alleen de diff te controleren, slaat de gate aan op wat de build net heeft toegevoegd of gewijzigd. -**`HYDRA_REVIEW_SCOPE=full`** in `secrets/.env` schakelt dit uit — gates draaien dan op de hele repo. Gebruik bij: -- **Onboarding van een nieuwe repo** in Hydra. -- Een **dedicated tech-debt-sweep**. +De flows leggen `--scope-to-diff` vast. **`HYDRA_REVIEW_SCOPE=full`** verandert alleen lokale en handmatige runs (`dev-run.sh`, `manual-review.sh`), waar de review dan de hele repo omvat. Gebruik dat bij: +- **Het onboarden van een nieuwe repo** in Hydra. +- Een **gerichte tech-debt-sweep**. -NIET voor reguliere PRs — verwacht veel rood. +NIET voor gewone PR's — verwacht veel rood. @@ -174,11 +204,11 @@ NIET voor reguliere PRs — verwacht veel rood.
-**3. Hoe herken je een fout-positieve gate, en wat is de juiste fix? Wat NIET?** +**3. Hoe herken je een vals-positieve gate, en wat is de juiste fix? Wat NIET?**
-Drie signalen samen wijzen op een fout-positief. En de "verleidelijke maar verkeerde" reflex is hetzelfde nóg een keer doen. +Drie signalen samen wijzen op een vals-positief. En de "verleidelijke maar verkeerde" reflex is hetzelfde nog een keer doen.
@@ -186,13 +216,13 @@ Drie signalen samen wijzen op een fout-positief. En de "verleidelijke maar verke **Herkenning** — drie signalen samen: -1. De gate slaat in elke retry op **dezelfde regel**, terwijl die regel onschuldig oogt. -2. **Handmatig lokaal de gate runnen** bevestigt: het patroon matched, maar niet om de bedoelde reden (bijv. `grep 'dd('` matcht ook `$builder->add(`). -3. Niemand in het team kan uitleggen waarom dit specifieke regel-niveau erover zou moeten klagen. +1. De gate slaat bij elke build aan op **dezelfde regel**, terwijl die regel onschuldig oogt. +2. **De gate lokaal draaien** bevestigt: het patroon matcht, maar niet om de bedoelde reden (bijvoorbeeld een tekstzoekopdracht die ook commentaar of een string-literal raakt). +3. Niemand in het team kan uitleggen waarom juist deze regel een klacht verdient. -**Juiste fix:** scherp de **gate-detectie** aan in `scripts/run-quality.sh` of de bijbehorende skill. Voorbeeld: `grep 'dd('` → `grep -wE '(^|[^A-Za-z0-9_])dd\('`. +**Juiste fix:** repareer de **gate-detectie** in het pakket `conduction/hydra-gates` (`ConductionNL/.github`, `hydra-gates/`). Hydra's `scripts/run-hydra-gates.sh` geeft alleen door. -**NIET:** nog een keer `retry:queued`. Dat reproduceert hetzelfde fout-positief en verspilt cycles. +**NIET:** de build opnieuw in de wachtrij zetten. Dat levert hetzelfde vals-positief op en verspilt een run. @@ -200,19 +230,18 @@ Drie signalen samen wijzen op een fout-positief. En de "verleidelijke maar verke
-**4. Waarom draait Hydra de mechanische gates nogmaals NA de reviewer-fixes (de "quality-recheck"-fase)?** +**4. Waar draait Hydra de gates automatisch opnieuw, en waar NIET?**
-De reviewers mogen binnen scope zelf fixen. Wat kan daarbij misgaan? +Denk aan de fix-ronde in de build-lane, en aan wat er gebeurt na een begrensde fix van een reviewer.
-Reviewers (Juan + Clyde) mogen binnen scope mechanische fixes pushen (ADR-021). Daarbij kan een reviewer **per ongeluk een nieuwe overtreding introduceren** — bijvoorbeeld een PHPCS-style fix die elders een regel breekt. - -De **quality-recheck** draait alle mechanische gates opnieuw op de uiteindelijke PR-staat zodat zeker is dat wat er nu staat ook objectief schoon is. Slaat de recheck rood → `needs-input`, geen retry. De reviewer is gestopt, de bouwer is gestopt, een mens kijkt. Het is de laatste deterministische gate vóór er een menselijke beslissing valt. +- **Wel:** in de build-lane. Faalt de eerste gate-run, dan volgt één fix-ronde, en daarna draaien de gates nog één keer over de gerepareerde tree. Die uitslag bepaalt `build:pass` of `build:fail`. +- **Niet:** na review. Een "quality-recheck" na review bestaat niet in de flows. Elke reviewstage moet de gates zelf draaien en `hydra-gates` opnemen in `checks_run`; een verdict dat dat niet doet, wordt niet geaccepteerd en het issue komt op `needs-input`.
@@ -227,7 +256,7 @@ De **quality-recheck** draait alle mechanische gates opnieuw op de uiteindelijke Date: Tue, 29 Sep 2026 15:45:22 +0200 Subject: [PATCH 2/2] docs(academy): hydra parts 4-7 match the flows, the images and the skill tree - Part 4: skill, gate and persona counts follow the repo, or are phrased so they do not go stale (about 96 gates, 9 personas). It says what the factory actually runs. The image table now says: builder copies .claude/skills/ only; reviewer and security carry hydra-gates plus 16 hydra-gate-* skills. Wrong skill descriptions are rewritten. - Part 5: a real run starts with build:queued and the change named in the issue body. Code review is queued by hand after build:pass. The label sequence has no :running, done or merge. Image defaults are localhost/hydra:test. The base image is COPY --from, not FROM. - Part 6: the recovery ladder uses the re-queue levers that work, and says a blocked review stage must lose its :queued label first. It states that retry:queued and rebuild:queued are not handled. The supervisor-log commands and the double-prefix example are gone. The hydra.json example shows what the record flow writes. - Part 7: hydra-sequencer polls; hydra-dispatch does not. There are no dependency or sibling checks. The owner comes from the hermiq workload step; the owner: marker is dropped because nothing in hydra parses it. The quiz drops "22 gates". English and Dutch (part 7 has no Dutch page). Co-Authored-By: Claude Opus 5.5 (1M context) --- .../index.mdx | 145 +++++----- .../index.mdx | 73 +++-- .../index.mdx | 260 +++++++++--------- .../index.mdx | 70 ++--- .../index.mdx | 207 +++++++------- .../index.mdx | 73 +++-- .../index.mdx | 254 +++++++++++------ 7 files changed, 611 insertions(+), 471 deletions(-) diff --git a/academy/2026-05-12-hydra-tutorial-4-skills/index.mdx b/academy/2026-05-12-hydra-tutorial-4-skills/index.mdx index c54f7cb14..e5fd205a4 100644 --- a/academy/2026-05-12-hydra-tutorial-4-skills/index.mdx +++ b/academy/2026-05-12-hydra-tutorial-4-skills/index.mdx @@ -5,7 +5,7 @@ contentType: tutorial heroIcon: pipeline authors: [conduction] date: 2026-05-12 -summary: Which skills run inside the automated Hydra factory, which exist for humans on the CLI, how they get invoked, and when you should add a new skill to the loop yourself. Fourth of six short modules. +summary: Which skills run inside the automated Hydra factory, which exist for humans on the CLI, how they get invoked, and when you should add a new skill to the loop yourself. Fourth of seven short modules. tags: [Hydra, Skills, OPSX, Testing, Tutorial series] apps: [] durationMinutes: 14 @@ -26,7 +26,7 @@ The previous parts were about *what* Hydra does. This part is about *how*: the * What a Claude Code skill is and how Hydra uses them to bundle behaviour onto personas. - Which skills the automated Hydra factory ships inside its Docker containers (per agent: Builder, Reviewer, Security) — and which skills are only for humans on the CLI. + What a stage in the automated Hydra factory is told to use, which skills the container images carry (Builder, Reviewer, Security) — and which skills are only for humans on the CLI. The five skill families inside Hydra: {'opsx-*'}, {'hydra-gate-*'}, {'team-*'}, {'test-*'}, and the utility/maintenance skills. The two ways a skill can be activated — manually via /{''} or automatically by Claude — and how you control that per skill. When to add a skill yourself and how to do that in a structured way. @@ -56,40 +56,44 @@ Which mode is allowed is set in the skill's own frontmatter: - `user-invocable: false` — only Claude itself may load it. Use this for background knowledge (for example a `legacy-system-context` that doesn't make sense as an action). - Neither set — both are allowed. -So that distinction — manual vs. automatic — is about **configuration of one skill**, not about two different kinds of files. A persona in a Docker container has no keyboard and leans heavily on auto-activation; a human on the CLI usually wants explicit control and types `/`. +So that distinction — manual vs. automatic — is about **configuration of one skill**, not about two different kinds of files. A pipeline stage has no keyboard: it gets a prompt that says what to do and which brief to follow, and anything else it loads is Claude's own choice. A human on the CLI usually wants explicit control and types `/`. ## Two worlds: the Hydra factory vs. skills for humans -Hydra's `hydra/.claude/skills/` contains ~70 skills, but **the automated pipeline only uses a handful**. The rest is tooling for you, behind a keyboard. Important to keep separate: +Hydra's `hydra/.claude/skills/` holds around 130 skill folders, about 60 of them `hydra-gate-*` (run `ls .claude/skills` for today's count). **The automated pipeline leans on very few of them.** The rest is tooling for you, behind a keyboard. Important to keep separate: ### What the Hydra factory actually runs -Every pipeline container has its own, limited set of skills baked into its Docker image: +A real run is driven by the `hydra-sequencer` flow (part 7). The hermiq app runs each stage as `claude -p` in a checkout of the target repo, with hydra's own tool tree beside it. The stage's prompt says what to do: + +- **Build (Al Gorithm)** — the prompt asks for the OpenSpec change named in the issue to be implemented. It does not name a skill. The agent may read and edit files (Read, Edit, Write, Glob, Grep) and is told not to run git; the pipeline commits and pushes. Then the pipeline itself runs `scripts/run-hydra-gates.sh --scope-to-diff`, allows one fix pass on the gate output, and runs the gates again. +- **Code review, security review, applier (Juan Claude, Clyde Barcode, Axel Pliér)** — each reads its own brief from `agents//` plus the stage brief under `images/`, runs the gates the way that brief says, and writes a verdict file. A verdict whose `checks_run` does not name `hydra-gates` is refused. + +The **container images** are what you start yourself with `dev-run.sh`, `smoke-test.sh` or `manual-review.sh` (part 5). Each has its own, limited set of skills baked in: | Container | Persona | Skills in image | Main function | |---|---|---|---| -| **Builder** | Al Gorithm | **All** `.claude/skills/*` + all vendor skills | Implements tasks; primarily `opsx-apply`. Has the other opsx skills on hand for context during verification, finding fits again, and fixing after a quality fail. | -| **Reviewer** | Juan Claude | `hydra-gates` + the `hydra-gate-*` skills + vendor `code-review` | Mandatory hydra-gates check + deeper code review via the Anthropic community skill. | -| **Security** | Clyde Barcode | `hydra-gates` + the `hydra-gate-*` skills + vendor `trailofbits` + vendor `owasp` | Mandatory hydra-gates + SAST (Semgrep) + OWASP top 10 checklists. | -| **Applier** | Axel Plier | **No skills, no write tools** (Read + Bash only) | Reads the final diff and both review verdicts and returns one binary answer — `pass` or `not`. It applies **no** fixes; it's a go/no-go gate, not an editor. | -| **Browser UI Tester** | (sonnet, headless) | One skill: `hydra-ui-test` | Logs in, navigates the live app via Playwright MCP, delivers verdict JSON. | +| **Builder** | Al Gorithm | **All** of `.claude/skills/` (no vendor skills) | Implements the change; the image's entrypoint follows `opsx-apply`. The other opsx skills are on hand for context. | +| **Reviewer** | Juan Claude | `hydra-gates` + a fixed selection of 16 `hydra-gate-*` skills + vendor `code-review` | Mandatory hydra-gates check + deeper code review via the Anthropic community skill. | +| **Security** | Clyde Barcode | `hydra-gates` + the same 16 `hydra-gate-*` skills + vendor `trailofbits` (its Semgrep plugin) + vendor `owasp` | Mandatory hydra-gates + SAST (Semgrep) + OWASP top 10 checklists. | +| **Applier** | Axel Pliér | **No skills**; its config allows Read and Bash only | Reads the final diff and both review verdicts and returns one binary answer — `pass` or `fail`. It applies **no** fixes; it's a go/no-go gate, not an editor. | -On top of that, **`scripts/run-hydra-gates.sh`** runs all **22** hydra-gates mechanically in every container, independent of which skill files are in the image. The skill files serve Claude as documentation when fixing; the script does the detection. +Whichever skill files an image carries, the detection is done by **`scripts/run-hydra-gates.sh`**. It is a thin delegator to the `conduction/hydra-gates` package, which declares about 96 gates today. That number grows with every incident, so don't trust a count written in a doc (this one included): every run prints a COVERAGE line with what it declared and what actually ran. The skill files serve Claude as documentation when fixing; the script does the detection. ### What the factory **doesn't** do — for humans on the CLI -All the other skills (~50 out of 70) are for you, or for a fellow dev in a Claude Code session on their laptop. Examples: +Nearly all the other skills are for you, or for a fellow dev in a Claude Code session on their laptop. Examples: -- **Preparing a change** — `opsx-new`, `opsx-ff`, `opsx-explore`, `opsx-plan-to-issues` are things you do as a human before you throw the work into the pipeline. The Builder only runs `opsx-apply` on an existing change. +- **Preparing a change** — `opsx-new`, `opsx-ff`, `opsx-explore`, `opsx-plan-to-issues` are things you do as a human before you throw the work into the pipeline. The pipeline only builds a change that already exists. - **Taking on a role** — `team-architect`, `team-backend`, `team-po` etc. are pure roleplay frames for one-human-one-role sessions. The pipeline doesn't use them. -- **Running tests** — `/test-counsel` (all 8 personas) or `/test-app` (one browser sweep) you start by hand. The factory has its own browser tester (`hydra-ui-test`); the `test-*` family is separate from that. +- **Running tests** — `/test-counsel` (all nine personas) or `/test-app` (one browser sweep) you start by hand. The pipeline runs no browser at all; the `test-*` family is yours. - **PRs and day-to-day work** — `/create-pr`, `/review-pr`, `/report-out` are the "three times a day" tools for you, not for the pipeline. -Remember: **the factory grabs 5 skills, a human can call all 70**. That's the whole difference. +Remember: **a pipeline stage follows its prompt, its brief and the gate suite; a human can call every skill in the folder**. That's the whole difference. ## The five skill families in full -With that split in mind, here are all five families. The tag system in the table: **🤖 factory** = ships inside a pipeline container, **⌨️ human** = CLI only. +With that split in mind, here are all five families. The tag system in the table: **🤖 factory** = used by the pipeline or by a pipeline image's entrypoint, **⌨️ human** = you call it yourself. (The builder image copies the whole `.claude/skills/` folder, so most ⌨️ skills are *present* in it; nothing in the pipeline calls them.) ### Family 1: OpenSpec workflow (`opsx-*`, 16 skills) @@ -97,19 +101,19 @@ The OPSX skills implement the Conduction workflow for OpenSpec changes — from | Skill | For whom | Does | |---|---|---| -| `opsx-apply` | 🤖 Builder | Implements tasks from a change (the pipeline default). | -| `opsx-verify` | 🤖 Builder | Verifies that the implementation covers the change artefacts. | -| `opsx-archive` | 🤖 Builder + ⌨️ | Archives a completed change, syncing delta into the spec. | +| `opsx-apply` | 🤖 Builder image + ⌨️ | Implements tasks from a change. The builder image's entrypoint follows it; the flow's build prompt does not name it. | +| `opsx-verify` | ⌨️ | Verifies that the implementation covers the change artefacts. | +| `opsx-archive` | ⌨️ | Archives a completed change, syncing delta into the spec. | | `opsx-new` | ⌨️ | Starts a new change (proposal scaffold, schema choice). | | `opsx-ff` | ⌨️ | "Fast-forward": creates a change + all artefacts in a single pass. | | `opsx-continue` | ⌨️ | Pick up an interrupted change. | | `opsx-explore` | ⌨️ | Explore pre-spec what a change should even be. | -| `opsx-onboard` | ⌨️ | Onboarding flow on a new repo (sets up OpenSpec config + structure). | -| `opsx-plan-to-issues` | ⌨️ | Converts `tasks.md` into GitHub issues + a tracking issue. | +| `opsx-onboard` | ⌨️ | Guided walk through one complete OpenSpec workflow cycle, with narration. | +| `opsx-plan-to-issues` | ⌨️ | Converts `tasks.md` into a `plan.json` and a tracking issue with task checkboxes. | | `opsx-sync` | ⌨️ | Sync delta specs back into the main spec. | | `opsx-bulk-archive` | ⌨️ | Archive multiple completed changes in one go. | -| `opsx-apply-loop` | ⌨️ | Headless build → quality-fix loop for local dev runs. | -| `opsx-pipeline` | ⌨️ | Run multiple changes in parallel (multi-agent). | +| `opsx-apply-loop` | ⌨️ | Runs apply → verify in a loop until verify passes, then archives; per app, in Docker. | +| `opsx-pipeline` | ⌨️ | Run multiple changes in parallel with subagents. | | `opsx-coverage-scan` | ⌨️ | Audit a legacy app for spec ↔ code coverage. | | `opsx-annotate` | ⌨️ | Applies `@spec` PHPDoc tags after a coverage scan. | | `opsx-reverse-spec` | ⌨️ | Reverse-engineers a spec from existing code. | @@ -118,22 +122,23 @@ Sixteen skills is a lot. In practice you reach for one of a handful, and the cho - **Starting fresh and sure of the feature?** → `opsx-ff` (one pass to all artefacts), then `opsx-plan-to-issues`. - **Starting fresh but unsure of scope/approach?** → `opsx-explore` first, then `opsx-new` and `opsx-continue` step by step. -- **New repo with no OpenSpec yet?** → `opsx-onboard`. +- **New to the workflow?** → `opsx-onboard` walks you through one full cycle. - **Legacy code with no specs?** → `opsx-coverage-scan` → `opsx-reverse-spec` / `opsx-annotate`. - **Change is written, want it built locally without the full factory?** → `opsx-apply-loop` (one change) or `opsx-pipeline` (several in parallel). -- **Inside the factory** the builder only ever runs `opsx-apply`, `opsx-verify` and `opsx-archive` — the rest are human-driven preparation. +- **Inside the factory** none of these is called by name: the flow's build prompt implements the change directly. The opsx skills are human-driven preparation and local tooling. ### Family 2: Quality + security gates (`hydra-gate-*`) -The parent skill **`hydra-gates`** is a dispatcher that calls every gate together. The script engine under the hood (`scripts/run-hydra-gates.sh`) is the single source of truth, and it currently runs **22 gates** (`gate-1` … `gate-22`) in every container; the per-gate skill files serve Claude as reference material when fixing a failure. +The parent skill **`hydra-gates`** runs the whole suite through `scripts/run-hydra-gates.sh` and summarises the failures. The gate logic itself lives in the `conduction/hydra-gates` package, which that script delegates to; the per-gate skill files serve Claude as reference material when fixing a failure. There are more gates than gate skills, and the numbering (up to gate-116 today) has gaps. -Rather than reproduce the full table here, it lives — accurate and numbered — in [part 3: Quality gates](/academy/hydra-tutorial-3-quality-gates#category-2-hydra-specific-gates). For the skill family it's enough to know the gates split into three rough groups: +Rather than reproduce a table here, the gates are covered in [part 3: Quality gates](/academy/hydra-tutorial-3-quality-gates#category-2-hydra-specific-gates). For the skill family it's enough to know the gates split into rough groups: -- **Hygiene** — `spdx-headers`, `forbidden-patterns`, `stub-scan`, `composer-audit`, `conflict-markers`. -- **Security & auth** — `route-auth`, `orphan-auth`, `no-admin-idor`, `unsafe-auth-resolver`, `semantic-auth`, `route-reachability`. -- **Conduction conventions & traceability** — `initial-state`, `admin-router`, `nc-input-labels`, `modal-isolation`, `dashboard-antipattern`, `redundant-controller`, `notification-dialect`, `or-objectservice-api`, `manifest-validation`, and the two traceability gates `spec-coverage` (gate-16) and `e2e-coverage` (gate-19). +- **Hygiene** — `spdx`, `forbidden-patterns`, `stub-scan`, `composer-audit`, `gitignore-then-commit`. +- **Security & auth** — `route-auth`, `orphan-auth`, `no-admin-idor`, `unsafe-auth-resolver`, `semantic-auth`, `route-reachability`, `csrf-cochange`. +- **Accessibility (WCAG 2.2 AA)** — `img-alt`, `button-name`, `form-label-association`, `html-lang`, `skip-link` and more. +- **Conduction conventions & traceability** — `initial-state`, `admin-router`, `nc-input-labels`, `modal-isolation`, `dashboard-antipattern`, `redundant-controller`, `notification-dialect`, `manifest-validation`, and the two traceability gates `spec-coverage` (gate-16) and `e2e-coverage` (gate-19). -All 22 run **🤖 inside the Hydra factory** (Reviewer + Security containers; Builder also runs them as a post-flight check during fix-quality). When a new incident produces a new gate, it's added here and the count grows — so trust the script's `ALL N GATES GREEN` line, not a hard-coded number in any doc. +In a flow run the pipeline runs the suite after the build and again after the single fix pass, and every review stage is told to run it. When a new incident produces a new gate, it's added here and the count grows — so trust the run's COVERAGE line, not a hard-coded number in any doc. ### Family 3: Team roles (`team-*`, 7 skills) — **⌨️ humans only** @@ -147,53 +152,53 @@ Skills that model a specific kind of work via a persona frame. Intended for a hu - `team-reviewer` — a manual variant of the reviewer work. - `team-qa` — writes test cases and test plans. -None of these ship in a pipeline container — they have no place in the automated loop. +The builder image carries them only because it copies the whole folder; nothing in the automated loop calls them. -### Family 4: Test suites (`test-*`, 19 skills) — almost all ⌨️ for humans +### Family 4: Test suites (`test-*`) — all ⌨️ for humans -The largest family by count: around twenty skills that drive agentic browser and API testing, in three clusters: +After the gates, the largest family: skills that drive agentic browser and API testing, in three clusters: **Test types** (one per test kind): - `test-app` — automated browser test of a whole Nextcloud app (Playwright MCP). - `test-functional` — functional scenarios against implemented features. -- `test-api` — API checks against the PHP built-in server. +- `test-api` — API checks (REST, NLGov conventions). - `test-accessibility`, `test-performance`, `test-security`, `test-regression` — specialised variants. -**Personas** (`test-persona-*`) — eight Dutch user profiles, each looking at an app from their own angle: `annemarie`, `fatima`, `henk`, `janwillem`, `mark`, `noor`, `priya`, `sem`. Henk reads with large type and looks for simple navigation; Noor hammers on RBAC and audit trails; Annemarie checks NLGov/GEMMA mapping. The persona cards themselves live in [`hydra/personas/`](https://github.com/ConductionNL/hydra/tree/main/personas). +**Personas** (`test-persona-*`) — nine Dutch user profiles, each looking at an app from their own angle: `annemarie`, `fatima`, `henk`, `janwillem`, `jasper`, `mark`, `noor`, `priya`, `sem`. Jasper works with a screen reader as his primary way in; Henk reads with large type and looks for simple navigation; Noor hammers on RBAC and audit trails; Annemarie checks NLGov/GEMMA mapping. The persona cards themselves live in [`hydra/personas/`](https://github.com/ConductionNL/hydra/tree/main/personas). **Scenario management** — `test-scenario-create`, `test-scenario-edit`, `test-scenario-run` write, edit and run reusable `TS-NNN-*.md` scenarios per app. -The dispatcher for this family is **`test-counsel`**: it coordinates all eight personas against one feature and delivers a combined report. Same pattern as `hydra-gates` for the quality-gates family. +The dispatcher for this family is **`test-counsel`**: it coordinates all nine personas against one feature and delivers a combined report. Same pattern as `hydra-gates` for the quality-gates family. -:::note Three things that all touch the browser — keep them apart +:::note Three things that sound like browser testing — keep them apart There are three separate "testing" surfaces and they're easy to confuse: -1. **`hydra-ui-test`** — the factory's own browser tester. Runs on the host (via `scripts/run-browser-tests.sh`, Playwright MCP), logs into the live app, walks the spec's acceptance criteria, and returns a verdict JSON. This is *runtime* proof that the feature works. +1. **`scripts/run-browser-tests.sh`** — a script that logs into a running app via Playwright MCP, walks the spec's acceptance criteria and returns a verdict JSON. It is *runtime* proof that the feature works, but only when you run it: no flow calls it, so a pipeline run brings no browser evidence. 2. **gate-19 (`e2e-coverage`)** — a *static* gate from [part 3](/academy/hydra-tutorial-3-quality-gates). It doesn't run a browser at all; it checks that every spec scenario is *linked* to a Playwright test via an `@e2e` annotation. Coverage bookkeeping, not execution. 3. **The `test-*` family** — human-driven test suites you run by hand before or after the pipeline. -So: gate-19 checks the *links* exist, `hydra-ui-test` actually *drives* the browser inside the factory, and `test-*` is what *you* run manually. Different jobs. +So: gate-19 checks the *links* exist and runs in the pipeline; the browser script and the `test-*` skills actually *drive* a browser, and both are things *you* start. Different jobs. ::: -### Family 5: Utility & maintenance (~13 skills) — almost all ⌨️ for humans +### Family 5: Utility & maintenance — all ⌨️ for humans -The rest — those are the remaining ~13 skills. Mostly dev comfort and meta work: +Everything else. Mostly dev comfort and meta work; a selection: | Skill | Does | |---|---| | `create-pr` | Creates a PR from a feature branch — local checks → branch pick → PR body. | -| `review-pr` | Reviews a GitHub PR (note: manual variant; the factory has its own Juan Claude). | +| `review-pr` | Reviews a PR (note: manual variant; the factory has its own Juan Claude). | | `report-out` | End-of-day report: today's commits + GitHub activity → Dutch Slack notification. | -| `clean-env` | Fully resets the local Docker dev environment. | -| `local-run` | Bring up the local Nextcloud dev environment. | +| `clean-env` | Resets the local OpenRegister dev environment (stop, remove volumes, restart, install apps). | +| `local-run` | Runs builder → reviewer → security locally on a throwaway repo; from Claude Code it does what `smoke-test.sh` does (part 5). | | `sync-docs` | Sync `{app}/docs/` or `.github/docs/claude/` with reality in the repo. | | `skill-creator` | Wizard for building a new skill (scaffold, frontmatter, evals). | -| `feature-counsel` | Pre-build spec analysis from eight persona perspectives (sibling of `test-counsel`). | -| `persistence-audit` | Audit how an app handles data persistence (object store, sessions, etc.). | +| `feature-counsel` | Pre-build spec analysis from nine persona perspectives (sibling of `test-counsel`). | +| `persistence-audit` | Audits a codebase or architecture for post-compromise persistence vectors: OAuth grants, service accounts, CI/CD secrets, IdP trust, audit trails. | | `journeydoc-init` / `journeydoc-add-story` / `journeydoc-instrument` | Manual instrumentation + extension of Journey docs. | | `verify-global-settings-version` | Check whether `global-settings/VERSION` was bumped after a change. | -Nothing in this family runs in the pipeline. They are your daily `/` commands. +Beside these sit writing skills (`writing`, `blog-write`, `tutorial-write`), the `parity-*` skills for capability matrices against competitors, and `atom-design` for design-system mocks. Nothing in this family runs in the pipeline. They are your daily `/` commands. ## Vendor skills (community) @@ -203,7 +208,7 @@ Next to its own skills, Hydra has **vendor skills** under `hydra/vendor/skills/` - `trailofbits` — Semgrep-based static-analysis methodology from Trail of Bits. → 🤖 Security container. - `owasp` — OWASP top 10:2025 + ASVS 5.0 checklists. → 🤖 Security container. -So those three **do** ship inside the factory (containers). They get loaded onto Juan and Clyde for extra coverage on top of our own `hydra-gate-*`. Maintenance sits with external parties — updates happen by tracking upstream, not by editing them yourself. +So those three **do** ship inside the reviewer and security images; the builder image carries none of them. They give Juan and Clyde extra coverage on top of our own `hydra-gate-*`. Maintenance sits with external parties — updates happen by tracking upstream, not by editing them yourself. ## When do you write a new skill? @@ -244,7 +249,7 @@ In the skill's frontmatter you set: - `user-invocable: false` — automatic only. Used for background knowledge that isn't useful as an action. - Both empty → both allowed. The default for most Hydra skills. -In the Hydra pipeline the containers mostly use auto-activation (Claude picks the right skill itself); a human on the CLI typically types `/` explicitly. +In the Hydra pipeline no one types a `/`: each stage gets a prompt that says what to do and which brief to follow, and any skill it loads beyond that is Claude's own choice. A human on the CLI typically types `/` explicitly. @@ -252,34 +257,38 @@ In the Hydra pipeline the containers mostly use auto-activation (Claude picks th
-**2. Which skills actually run inside the pipeline containers, and which ones sit in the repo only for humans?** +**2. What does the automated pipeline actually use, which skills do the container images carry, and which ones sit in the repo only for humans?**
-Three containers (Builder, Reviewer, Security) each have a specific set in their image. What's inside — and which families sit entirely outside? +Separate the flow-driven run (prompt, brief, gate suite) from the images you start yourself. What does each image carry — and which families sit entirely outside?
-**Inside the pipeline containers** (🤖 factory): +**In a flow-driven run** (🤖 factory), a stage follows its prompt and brief: + +- **Build** — the prompt asks for the change to be implemented directly; no skill is named. The pipeline then runs `scripts/run-hydra-gates.sh`, allows one fix pass, and runs the gates again. +- **Review stages** — each persona reads its brief from `agents//` and `images/`, runs the gates, and must list `hydra-gates` in `checks_run`. + +**In the container images** (what `dev-run.sh` and `smoke-test.sh` start): -- **Builder** — all `.claude/skills/*` baked in, but the standard run is `opsx-apply`. The other opsx skills are there for context. -- **Reviewer** — `hydra-gates` + 13 individual `hydra-gate-*` + vendor `code-review`. -- **Security** — `hydra-gates` + 13 individual `hydra-gate-*` + vendor `trailofbits` + vendor `owasp`. -- **Applier** — no skills (pure CLAUDE.md). -- **Browser UI Tester** — only `hydra-ui-test`. +- **Builder** — all of `.claude/skills/`, no vendor skills. Its entrypoint follows `opsx-apply`. +- **Reviewer** — `hydra-gates` + 16 individual `hydra-gate-*` + vendor `code-review`. +- **Security** — `hydra-gates` + the same 16 `hydra-gate-*` + vendor `trailofbits` (Semgrep) + vendor `owasp`. +- **Applier** — no skills, Read and Bash only. -Plus: `scripts/run-hydra-gates.sh` runs all **14** gates mechanically in every container, regardless of which skill files are in the image. +Whatever the image carries, `scripts/run-hydra-gates.sh` does the detection. It declares far more gates than there are gate skills; the run's COVERAGE line says how many. -**Not in the factory**, only for humans on the CLI: +**Not used by the factory** (some are present in the builder image, but no stage calls them), only for humans on the CLI: -- Almost the entire `team-*` family (7 skills). -- Almost the entire `test-*` family (19 skills) — the factory has its own `hydra-ui-test`. -- Most `opsx-*` skills except `opsx-apply`/`verify`/`archive` (humans start changes; the pipeline implements them). -- The whole utility family (`create-pr`, `review-pr`, `report-out`, `clean-env`, `sync-docs`, `skill-creator`, `feature-counsel`, `persistence-audit`, `journeydoc-*`, `verify-global-settings-version`). +- The entire `team-*` family. +- The entire `test-*` family — the pipeline runs no browser at all. +- The `opsx-*` skills (humans prepare changes; the pipeline builds them). +- The whole utility family (`create-pr`, `review-pr`, `report-out`, `clean-env`, `local-run`, `sync-docs`, `skill-creator`, `feature-counsel`, `persistence-audit`, `journeydoc-*`, `verify-global-settings-version`, and the writing and parity skills). -In total: of ~70 skills in the repo, the automated pipeline uses a handful; the rest is your toolkit. +In total: of the roughly 130 skills in the repo, the automated pipeline leans on the gate suite and a brief per persona; the rest is your toolkit.
@@ -318,7 +327,7 @@ Think about provenance (who wrote them?), which pipeline container they get load
-Vendor skills (`hydra/vendor/skills/`) are skills that were **not written by us**, and that **do** ship inside the factory: +Vendor skills (`hydra/vendor/skills/`) are skills that were **not written by us**, and that **do** ship inside the reviewer and security images: - `code-review` (Anthropic community) → Reviewer container (Juan Claude). - `trailofbits` (Trail of Bits, Semgrep methodology) → Security container (Clyde). @@ -333,11 +342,11 @@ We keep them separate because:
- + # from `claude setup-token`; or ANTHROPI There is no credentials file. `scripts/lib/env-credentials.sh` is the one resolver, and it fails closed: when a variable is missing, the script stops with a reason instead of starting a run that dies three seconds later with a 401. The scripts pass both values to the containers by variable name (`-e GH_TOKEN`), so the values never end up in the process list or in a file. - A Claude Max or Pro subscription is personal-scope: it may run your own work, not an organisation's. Locally that takes care of itself, because the script runs under your shell with your token. In the pipeline, the flows refuse to run a stage whose owner cannot be established. Part 7 explains how. + A Claude Max or Pro subscription is personal-scope: it may run your own work, not an organisation's. Locally that takes care of itself, because the script runs under your shell with your token. In the pipeline, the owner of each stage comes from the step that runs it, and hydra-record-stage refuses to record a stage without one. Part 7 explains more. :::note Older setups @@ -81,9 +81,9 @@ HYDRA_IMAGE_TAG=test HYDRA_REVIEW_SCOPE=diff ``` -`HYDRA_FORGE=github` is right for every ConductionNL repo; `forgejo` is only for a customer who runs a self-hosted Forgejo. It has no default because guessing a forge means sending your token to a third party. If you start from `secrets/.env.example`, check that line: older copies set it to `codeberg`. +`HYDRA_FORGE=github` is right for every ConductionNL repo; `forgejo` is only for a customer who runs a self-hosted Forgejo. It has no default because guessing a forge means sending your token to a third party. `secrets/.env.example` sets it to `github`; if you copied an older example, check that line, because older copies set it to `codeberg`. -Tip: the image defaults are `ghcr.io/conductionnl/hydra` as prefix and `latest` as tag. Leave `HYDRA_IMAGE_*` out to use the published images. +Tip: the scripts that read `HYDRA_IMAGE_*` (`manual-review.sh`, `cron-audit.sh`, `cron-spec-from-issue.sh`) fall back to `localhost/hydra` and `test` when the variables are unset, and `dev-run.sh` always starts `localhost/hydra-:test`, whatever you set. So locally you build the images yourself (step 4), or pull a published `ghcr.io/conductionnl/hydra-` image and tag it under the local name. :::tip secrets/.env is loaded automatically `scripts/dev-run.sh` refuses to start without this file and loads it itself, and so do `reconcile.sh` and the `cron-*.sh` sweeps. For a while `dev-run.sh` only checked that the file existed and never loaded it, so `HYDRA_FORGE=github` looked configured while every container still talked to Codeberg ([hydra#391](https://github.com/ConductionNL/hydra/issues/391)). Every entrypoint that reads configuration MUST source this file at startup; that's a repo-wide contract. @@ -91,7 +91,7 @@ Tip: the image defaults are `ghcr.io/conductionnl/hydra` as prefix and `latest` ## Step 4: build the container images -In CI, GitHub Actions pushes all images to GHCR. Locally, with `HYDRA_IMAGE_PREFIX=localhost/hydra` set, you build them yourself: +In CI, the `hydra-image.yml` workflow builds the base, builder, reviewer and security images and pushes them to GHCR as `ghcr.io/conductionnl/hydra-*`. Locally you build them yourself, under the names the scripts look for: ```bash # Base image: Nextcloud + PostgreSQL + OpenRegister (builder dependency) @@ -107,7 +107,7 @@ docker build -t localhost/hydra-security:test -f images/security/Dockerfile . docker network create hydra-net ``` -Then check with `docker images | grep hydra` that the three tags are there. Reviewer and security are standalone (no NC dependency); the builder builds on the `hydra-nextcloud-test` base. +Then check with `docker images | grep hydra` that the three tags are there. Reviewer and security are standalone (no NC dependency). The builder copies the Nextcloud tree and its database out of `ghcr.io/conductionnl/hydra-nextcloud-test:stable32` with `COPY --from`, so that image has to exist under exactly that name before you build the builder. ## Step 5: run one stage with `dev-run.sh` @@ -144,21 +144,24 @@ The script renames an older `todo-app` to `todo-app-runN`, creates a fresh one w ## Step 7: a real run on a target app -On a real app you don't start containers yourself. You put the trigger label on the tracking issue and the flows do the rest: +On a real app you don't start containers yourself. You put the trigger label **`build:queued`** on the issue and the flows do the rest. Two things have to be true first: -- The trigger label `ready-to-build`. -- Optionally `yolo`, if the PR may auto-merge once everything is green. -- Optionally `openspec` if you're working via OpenSpec. +- The issue **body** names the change as a path, `openspec/changes/`. The sequencer takes the first such path it finds. No path means `build:blocked`. +- That change directory exists on the base branch: `development` if the repo has one, otherwise the default branch. If it doesn't, the result is `build:blocked` as well. -Via the Hydra helper: +Then set the label via the Hydra helper: ```bash -./scripts/hydra-label.sh ConductionNL/ add ready-to-build +./scripts/hydra-label.sh ConductionNL/ add build:queued ``` `scripts/hydra-label.sh` is the **preferred path**: it uses the label helpers from `scripts/lib/labels.sh` and syncs the GitHub Project board in real time. A direct `gh issue edit --add-label` works too, but then the board stays in the old column until reconcile (every 10 minutes) picks it up. -The `hydra-dispatch` flow finds the label on its next five-minute tick and hands the issue to `hydra-sequencer`. Its definition ships with `enabled: false`, so this only happens on an OpenRegister instance where the flow has been switched on. Part 7 covers that setup. +The `hydra-sequencer` flow finds the label on one of its five-minute ticks. Each tick it searches the org for open issues with a queued label and does one unit of work; review labels go before `build:queued`, so a busy queue can take a few ticks to reach yours. Its definition ships with `enabled: false`, so this only happens on an OpenRegister instance where the flow has been switched on. Part 7 covers that setup. + +:::note ready-to-build and yolo +Older versions of this part used `ready-to-build` as the trigger and `yolo` for an auto-merge. `create-labels.sh` still creates both labels, but no flow reads either of them. The trigger is `build:queued`, and nothing merges automatically. +::: Earlier versions of this part spent a whole step on HYDRA_LABEL_PREFIX. It gave each developer's local supervisor its own label namespace (wilco-build:queued), so two supervisors watching the same repo did not pick up each other's work. The supervisor is gone, and the flows are the only thing left that picks up work. They act on the bare labels only. The shell tooling (create-labels.sh --prefix, hydra-label.sh, reconcile.sh) still understands the variable, but no flow picks up a prefixed label. Leave it unset. @@ -166,23 +169,32 @@ The `hydra-dispatch` flow finds the label on its next five-minute tick and hands ## Step 8: follow the pipeline -Once the sequencer has the issue, this happens: +Once the sequencer has the issue, the labels move like this: ``` -ready-to-build → build:queued → build:running → build:pass - ↳ code-review:queued → :running → :pass / :fail - ↳ security-review:queued → :running → :pass / :fail - ↳ applier:queued → :running → :pass / :fail → done / needs-input +build:queued → build:pass | build:fail | build:no-change | build:blocked + (a PR is opened on pass and on fail; it is never merged) + +build:pass → you add code-review:queued yourself + +code-review:queued → code-review:pass (+ security-review:queued) | code-review:fail | needs-input +security-review:queued → security-review:pass (+ applier:queued) | security-review:fail | needs-input +applier:queued → applier:pass | applier:fail | needs-input ``` -You follow it in two places: +A few things that are easy to expect and do not happen: + +- **Nothing queues code review after `build:pass`.** Add it yourself: `./scripts/hydra-label.sh ConductionNL/ add code-review:queued`. +- **There are no `:running` labels.** While a stage runs, its `:queued` label stays on the issue and a lock marks the issue as in flight. +- **A `fail` stops the chain.** A failed code review does not go on to security review. +- **`applier:pass` is the end.** There is no `done` label and no merge; a human merges the PR. -1. **GitHub issue**: the labels change as the sequencer advances the stages. -2. **GitHub PR**: the builder's commits and the reviewers' inline comments. +You follow it in two places: -Behind both, every stage is recorded in `openspec/changes//hydra.json` before its label moves. Part 6 shows how to read that file. The flow runs themselves live in OpenRegister; there is no log file on your machine to tail. +1. **GitHub issue**: the labels change as the sequencer advances the stages. The sequencer writes those labels itself, so the project-board card does not follow straight away; `reconcile.sh` moves it on its next sweep. +2. **GitHub PR**: the build commit on `feature//hydra-build`, with the gate output in the PR body. Each review stage writes its verdict to a `hydra-verdict.json` file that the sequencer collects on the next tick; that verdict is what moves the labels. -Set `yolo` and is everything green? Then you'll see `done` appear on the issue, an approval on the PR, and an auto-merge. No `yolo`? Then the PR is ready for a human to merge. +Behind both, after each stage's labels have moved, the sequencer records the stage in `openspec/changes//hydra.json` through the `hydra-record-stage` flow, which refuses a stage that names no owner. Part 6 shows how to read that file. The flow runs themselves live in OpenRegister; there is no log file on your machine to tail. ## Troubleshooting @@ -196,17 +208,20 @@ Set `yolo` and is everything green? Then you'll see `done` appear on the issue, Add HYDRA_FORGE=github to secrets/.env. The refusal is deliberate: without it, Hydra would have to guess where to send your token. - - Check that secrets/.env actually contains HYDRA_IMAGE_PREFIX=localhost/hydra and HYDRA_IMAGE_TAG=test, and that the tags from step 4 match. + + dev-run.sh always uses localhost/hydra-<stage>:test and ignores HYDRA_IMAGE_*, so build with exactly those tags. For manual-review.sh and the cron scripts, check what HYDRA_IMAGE_PREFIX and HYDRA_IMAGE_TAG in secrets/.env resolve to; unset, they mean localhost/hydra and test. - docker images | grep hydra-builder. No hit? Build the base image first (hydra-nextcloud-test) and then the builder. The builder Dockerfile has the base as FROM. A network hydra-net not found error means you skipped the last line of step 4. + docker images | grep hydra-builder. No hit? Build the base image first (hydra-nextcloud-test) and then the builder. The builder Dockerfile copies from the base with COPY --from, so the base has to exist under the name ghcr.io/conductionnl/hydra-nextcloud-test:stable32. A network hydra-net not found error means you skipped the last line of step 4. - Nothing on your machine watches labels. On a real app the hydra-dispatch flow has to be switched on in the OpenRegister instance (part 7). To try a stage locally, use dev-run.sh from step 5 instead. + Nothing on your machine watches labels. On a real app the hydra-sequencer flow has to be switched on in the OpenRegister instance (part 7). Check the label's exact name as well: with HYDRA_LABEL_PREFIX set, hydra-label.sh writes <prefix>-build:queued, which no flow reads. And the sequencer does one unit of work per tick, review stages first, so a full queue delays your build. To try a stage locally, use dev-run.sh from step 5 instead. + + + The sequencer could not find the change. The issue body has to contain openspec/changes/<name>, and that directory has to exist on development (or on the default branch, if the repo has no development). Fix the body or push the change, then remove build:blocked and add build:queued again. - - Stale labels from a previous run are still hanging around. Strip the old stage labels with ./scripts/hydra-label.sh ConductionNL/<app> <N> transition ready-to-build. That puts the issue back into a defined starting state. + + Stale review labels from a previous run are still on the issue, and the sequencer takes those before build:queued. Reset the issue with ./scripts/hydra-label.sh ConductionNL/<app> <N> transition build:queued: that strips every other queued label, needs-input and the old build verdict in one call, and leaves only build:queued as the starting state. @@ -292,7 +307,7 @@ What does a run leave behind when no flow is involved? - The three typical causes of a stuck pipeline: stale dependencies, legitimate review findings, and fundamentally wrong build approach. - The intervention ladder: development merge → reset review labels → retry:queued → rebuild:queued → manual fix. - Why needs-input is NOT the same as "Hydra is broken". - Where to find the retrospectives and how to recognise recurring patterns. + What each outcome means: the four build results, and needs-input, which only a review stage sets. + The intervention ladder: update a stale PR → queue or re-queue a review stage → unblock a needs-input stage → re-queue the build → manual fix. + Why retry:queued and rebuild:queued do nothing today. + Where to look when something is stuck, and how to recognise recurring patterns.
Parts 1 through 5 worked through. - A mental model of the label state machine (from part 2). + A mental model of the label sequence from step 8 of part 5. -## What does `needs-input` mean? +## What do the outcomes mean? -`needs-input` is **not a failure state in the sense of "Hydra has failed"**. It's an explicit escalation: Hydra has done what it could, one run, and has reached a terminal point where it can't proceed on its own without a human decision. +The two lanes of `hydra-sequencer` end differently, and it helps to keep them apart. -Possible causes: +**The build lane** ends in one of four labels. None of them is `needs-input`: -- Both reviewers `pass` but the quality recheck after their fixes is red (a fix introduced a new violation). -- One of the two reviews `:fail`. -- Applier Axel Pliér `:fail`. -- Build crashed mid-stage (container OOM, rate-limit on all accounts, …). -- Agent-maxed-out: a persona used up its turn budget. +| Label | What happened | Is there a PR? | +|---|---|---| +| `build:pass` | The gates were green, straight away or after the one fix pass. | Yes | +| `build:fail` | The gates were still red after the one fix pass. | Yes, with the gate output in its body | +| `build:no-change` | The agent changed nothing, so there was nothing to push. | No | +| `build:blocked` | The issue body names no `openspec/changes/`, that change does not exist on the base branch, or the build could not run. | No | -Your role as a human: decide which of the four options below is the best fit and apply it. **Don't** blindly slap on another `retry:queued` in the hope that this time it works. +**The review lane** (code review → security review → applier) ends each stage in `:pass`, `:fail` or `needs-input`. `needs-input` is **not a verdict on the code**. It means the stage produced no usable verdict: + +- the verdict file `hydra-verdict.json` is missing or unreadable; +- the verdict does not list `hydra-gates` in `checks_run`, so there is no proof the gates ran; +- the stage could not run at all. + +A `:fail` is a real verdict: the reviewer looked and said no. Nothing further is queued after a `:fail`, and after `build:pass` nothing is queued at all: you add `code-review:queued` yourself. + +Your role as a human: find out which of these you are looking at, and pick the cheapest lever below that fits. ## The intervention ladder +Every lever is a label. Set it with `scripts/hydra-label.sh`, so the project board follows. + | # | Situation | Intervention | Cost | What you keep | |---|---|---|---|---| -| 1 | Development has moved on, PR is stale (new lint rules, ADRs, fixtures on development) | gh pr update-branch <N> (or the cron picks it up) | Free | All build/review/applier work. Just refresh dependencies. | -| 2 | Review contracts were adjusted and you want reviewers to judge again without a rebuild | Reset reviewer labels: strip stage labels, set code-review:queued again. PR stays. | 2× reviewer + applier | The builder output. Only the review part rewinds. | -| 3 | Reviewers/applier flagged concrete findings and the builder just needs to polish | Remove needs-input + fail labels first, then add retry:queued (see below) | 1× builder + 2× reviewer + applier | Branch + PR stay. Orchestrator builds feedback.md from hydra.json (unfixed + applier blockers + already-fixed) and dispatches builder in HYDRA_MODE=fix. Single shot — no loop. | -| 4 | Builder output was fundamentally wrong (wrong approach, stub implementation, missed core requirements) | Remove needs-input + fail labels first, then add rebuild:queued (see below) | Full cycle | Orchestrator closes the open PR, hard-resets feature/<issue>/* to development, strips all cycle labels, sets build:queued. Next cycle starts from scratch. | -| 5 | You closed the PR yourself and stale labels are still on the issue | Wait for reconcile.sh (every 10 min) | Next reconcile run | check_stage_without_open_pr sees the inconsistency and automatically sets build:queued. Self-healing. | -| 6 | Pipeline infra is broken (container crash, all tokens exhausted, image not findable) | Escalate to a human (you or a team lead), fix infra, and only THEN pick one of 1-4 above | Time | — | - -**Order:** always 1 → 2 → 3 → 4. Take the cheapest recovery first. Reach for `rebuild:queued` only if the build itself was fundamentally wrong. +| 1 | Development has moved on and the PR is stale (new lint rules, ADRs, fixtures on development) | gh pr update-branch <N>, or wait for cron-update-prs.sh (hourly) | Free | Everything. Only the base is refreshed. | +| 2 | build:pass and nobody reviewed yet | Add code-review:queued | Up to three review stages | The build. | +| 3 | Review contracts changed, or a review should run again | Remove that stage's :pass/:fail (and needs-input), add <stage>:queued | One to three review stages | The build. On a pass the sequencer queues the next stage again. | +| 4 | A review stage ended in needs-input | **First** remove that stage's :queued label, fix the cause, then remove needs-input and add :queued again (see below) | One stage | The build and the earlier verdicts. | +| 5 | The build went wrong: build:fail, build:no-change, build:blocked, or a build:pass that is wrong anyway | Remove the build:* verdict, add build:queued | A full build | Nothing from the old build. The sequencer builds again from the base branch and pushes feature/<issue>/hydra-build. | +| 6 | You closed the PR yourself and stale labels are still on the issue | Wait for reconcile.sh (every 10 min) | Next reconcile run | check_stage_without_open_pr sees the inconsistency and sets build:queued. Self-healing. | +| 7 | Pipeline infra is broken (flow switched off, a workload step that cannot start, a credential the broker cannot resolve) | Fix the infra first, and only THEN pick one of 1–5 | Time | — | -### `retry:queued` in detail +**Order:** always the cheapest that fits. Reach for a new build only if the build itself was wrong; otherwise you throw away good work. -:::warning Label cleanup is required first -The supervisor **will not dispatch `retry:queued` while `needs-input` is still on the issue.** You must remove stale labels before applying the trigger — otherwise the issue sits in the queue forever and nothing moves. -::: +### Re-queueing a review stage -**Before adding `retry:queued`, run these commands** (use `scripts/hydra-label.sh` — it keeps the project board in sync): +Say the code review failed and you think it should run again, because an ADR changed or you pushed a fix to the branch by hand: ```bash -# Remove stale labels -./scripts/hydra-label.sh ConductionNL/ remove needs-input -./scripts/hydra-label.sh ConductionNL/ remove code-review:fail -./scripts/hydra-label.sh ConductionNL/ remove security-review:fail - -# Now apply the trigger -./scripts/hydra-label.sh ConductionNL/ add retry:queued +./scripts/hydra-label.sh ConductionNL/ remove code-review:fail +./scripts/hydra-label.sh ConductionNL/ add code-review:queued ``` -If you're running with `HYDRA_LABEL_PREFIX` (e.g. `wilco`), strip the prefixed variants too: +The sequencer picks it up on one of its next ticks. If the review passes, it adds `security-review:queued` itself, and the chain continues from there. -```bash -./scripts/hydra-label.sh ConductionNL/ remove wilco-needs-input -./scripts/hydra-label.sh ConductionNL/ remove wilco-code-review:fail -./scripts/hydra-label.sh ConductionNL/ remove wilco-security-review:fail -./scripts/hydra-label.sh ConductionNL/ add wilco-retry:queued -``` +### A review stage that ended in `needs-input` -See the retry-and-rebuild operations guide in [`hydra/docs/operations/`](https://github.com/ConductionNL/hydra/tree/main/docs/operations) for the full checklist and `rebuild:queued` equivalent. +:::warning Remove the `:queued` label first +A stage that ends in `needs-input` **keeps its `:queued` label**. The sequencer only looks for that label, so it picks the stage up again on the next tick — every five minutes — until someone removes it. Take the label off before you start investigating. +::: -**What happens next:** +```bash +# 1. Stop the re-runs +./scripts/hydra-label.sh ConductionNL/ remove security-review:queued -1. Supervisor picks up `retry:queued` as a regular queue job. -2. Orchestrator finds the open PR on `feature//*`. -3. Orchestrator clones development, copies `openspec/changes//hydra.json` and runs `scripts/lib/build-feedback-brief.py` → writes `feedback.md` with (a) applier blockers, (b) unfixed reviewer findings, (c) already-fixed items, (d) Scope list of all flagged files. -4. Orchestrator flips `retry:queued` → `retry:running`, dispatches the builder with `HYDRA_MODE=fix` and `feedback.md` mounted at `/workspace/feedback.md`. -5. Builder reads the brief, fixes **all** findings in one pass, restricts itself to Scope files (plus new files explicitly required by a blocker), commits with `fix (retry):` messages, pushes. -6. Success: orchestrator strips all review/applier labels + `retry:running`, sets `code-review:queued`. Reviewers run again against the fixed code. -7. Failure: `retry:running` → `needs-input`, a comment on the issue explains why and points to `rebuild:queued` as the next lever. +# 2. Fix the cause: gates not run, no verdict file, or a stage that could not start -**No loop.** One `retry:queued` = one iteration. If the fix builder pushes and the next review fails again, you can request another `retry:queued` — but that's then a **new** single-shot, not an auto-retry. +# 3. Queue it again +./scripts/hydra-label.sh ConductionNL/ remove needs-input +./scripts/hydra-label.sh ConductionNL/ add security-review:queued +``` -### `rebuild:queued` in detail +Always pass the **bare** label name. With `HYDRA_LABEL_PREFIX` set, `hydra-label.sh` adds the prefix itself, so `wilco-needs-input` would become `wilco-wilco-needs-input`. And since the flows only read bare labels, leave the prefix unset anyway (part 5). -Same label cleanup requirement as `retry:queued` — the supervisor won't dispatch while `needs-input` is set: +### Re-queueing the build ```bash -./scripts/hydra-label.sh ConductionNL/ remove needs-input -./scripts/hydra-label.sh ConductionNL/ remove code-review:fail -./scripts/hydra-label.sh ConductionNL/ remove security-review:fail -./scripts/hydra-label.sh ConductionNL/ add rebuild:queued +./scripts/hydra-label.sh ConductionNL/ remove build:fail +./scripts/hydra-label.sh ConductionNL/ add build:queued ``` -Do **not** close the PR yourself first — the orchestrator closes it as part of the reset sequence. If you already closed it, `reconcile.sh` (runs every 10 min) will detect the inconsistency and auto-set `build:queued`. +Replace `build:fail` with whichever build verdict is on the issue. If there are also old review labels on it, `./scripts/hydra-label.sh ConductionNL/ transition build:queued` does it in one call: it strips every other queued label, `needs-input` and the old build verdict, and leaves `build:queued`. + +What the push does with an existing `feature//hydra-build` branch, and whether a new PR is opened next to an old one, is decided outside the flow definitions. If you want a clean PR, close the old one first. + +### What about `retry:queued` and `rebuild:queued`? + +**They do nothing today.** No flow reads either label. Earlier versions of this part described what they did under the retired shell orchestrator: `retry:queued` compiled a `feedback.md` from `hydra.json` and ran the builder once more in `HYDRA_MODE=fix`, and `rebuild:queued` closed the PR and hard-reset the branch. That orchestrator is gone, and so is that behaviour; `scripts/lib/build-feedback-brief.py` has no caller. + +`create-labels.sh` still creates both labels, and `cron-update-prs.sh` still puts `rebuild:queued` on the issue of a PR with merge conflicts, together with a comment. Nothing consumes that label, so treat the comment as the signal: resolve the conflict, or re-queue the build (rung 5). ## When is it the gate, and when is it the builder? -A recurring pitfall from our own retrospectives: the issue goes to `needs-input` 3 times for the same reason — typically `spdx-headers: fail` or a similar mechanical gate. Each `retry:queued` reproduces the same pattern. +A recurring pitfall from our own retrospectives: the same issue fails three times on the same mechanical gate — typically a missing SPDX header or a similar hygiene check. Each re-queued build reproduces the same pattern. -In that case a fourth retry is **not** the right move. Be disciplined: +In that case a fourth build is **not** the right move. Be disciplined: -1. **Sanity-check the gate itself.** Is this a false positive? (See part 3.) A classic: a grep without a word-boundary that matches more than intended. In that case fix `scripts/run-quality.sh` or the gate skill. -2. **Hand-fix the mechanical violation.** Sometimes it's faster to add the SPDX headers yourself, commit on the feature branch, and call the reviewers again via a label reset (situation 2 above). Time: minutes. -3. **Consider a stronger agent.** If the same class of mistake shows the same "blind spot" across multiple repos, that's a signal that the current builder prompt or the mechanical gate isn't enough. Open an issue in `hydra/` to tighten the gate or build a "housekeeping" agent. +1. **Sanity-check the gate itself.** Is this a false positive? (See part 3.) A classic: a grep without a word boundary that matches more than intended. The gates live in the `conduction/hydra-gates` package (in `ConductionNL/.github`); fix the gate there, or its skill in `hydra/`. +2. **Hand-fix the mechanical violation.** Often it's faster to add the SPDX headers yourself, commit on `feature//hydra-build`, and queue the code review (rung 2 or 3). Time: minutes. +3. **Consider a stronger agent.** If the same class of mistake shows the same blind spot across multiple repos, that's a signal that the build prompt or the mechanical gate isn't enough. Open an issue in `hydra/` to tighten the gate or the brief. ## Watch-list patterns -Patterns we've already seen and that are worth comparing against your situation first: +Patterns we've already seen and that are worth comparing against your situation first. The first three come from the shell pipeline that the flows replaced; the lessons still hold. - On decidesk (now Decidiq) GitHub issues #44/#71/#72, the same pattern: builder missed the EUPL-1.2 header on new PHP files, reviewer claimed 0 fixes needed, recheck went red. Forbidden-patterns turned out to be partly false positive (grep without word-boundary). Fix: manually added SPDX + tightened the grep. + On decidesk (now Decidiq) GitHub issues #44/#71/#72, the same pattern: builder missed the EUPL-1.2 header on new PHP files, reviewer claimed 0 fixes needed, the recheck went red. Forbidden-patterns turned out to be partly false positive (grep without word-boundary). Fix: manually added SPDX + tightened the grep. @@ -136,88 +141,76 @@ Patterns we've already seen and that are worth comparing against your situation - Completion handler in a tight loop posted a new escalation comment every tick on an already-terminal issue. Fix: terminal-state guard before all side-effects (see CLAUDE.md section "Terminal-state guards"). If you see an issue with >3 identical needs-input comments: open an infra bug, not a retry. + A completion handler in a tight loop posted a new escalation comment every tick on an already-terminal issue. Fix: a terminal-state guard before all side-effects (see CLAUDE.md section "Terminal-state guards"). The sequencer has no such guard today: a blocked review stage is not re-commented but re-run every tick, until its :queued label goes. If you see the same stage running over and over, remove that label and open an infra issue; don't re-queue anything. ## Diagnostics: where do you look? -Quick checklist when an issue goes to `needs-input`: +There is no log file on your machine to tail. Quick checklist when an issue is stuck: ```bash # 1. Which labels are currently set? gh issue view --repo ConductionNL/ --json labels --jq '[.labels[].name]' -# 2. What does hydra.json say (if present on the feature branch)? -gh api repos/ConductionNL//contents/openspec/changes//hydra.json?ref=feature// \ - --jq '.content' | base64 -d | jq '.cycles[-1] | {outcome, outcome_reason, pattern_tags}' - -# 3. The supervisor log for the relevant time window -grep "issue/" logs/supervisor.log | tail -50 +# 2. Is there a PR, and what does its body say (gate output)? +gh pr list --repo ConductionNL/ --head feature//hydra-build --state all -# 4. Pipeline logs (per stage) on the feature branch -gh api repos/ConductionNL//contents/openspec/changes//pipeline-logs?ref=feature// +# 3. What does hydra.json on the feature branch say about the last recorded stage? +gh api "repos/ConductionNL//contents/openspec/changes//hydra.json?ref=feature//hydra-build" \ + --jq '.content' | base64 -d | jq '.cycles[-1]' ``` -`hydra.json` is your most important source. The `outcome_reason` and `pattern_tags` of the last cycle usually explain in a single line what went wrong. +The fourth place is the **flow runs** of `hydra-sequencer` in the OpenRegister instance: every tick is one run there. Look there when the labels and `hydra.json` don't explain what happened — for instance when a stage could not start, or its verdict could not be read. ### Reading hydra.json (schema v2) -The file is written stage-by-stage by `scripts/lib/hydra_record.py` ("record-or-die" — a stage that doesn't persist its record fails the pipeline). The shape you'll be reading is: +After a stage's labels have moved, the sequencer calls the `hydra-record-stage` flow, which appends an entry to `openspec/changes//hydra.json` on `feature//hydra-build` and commits it through the API. It refuses a stage without an owner: the record has to say who ran it, on whose credential. Each recorded stage becomes a new entry in `cycles[]`: ```jsonc { "schema_version": 2, - "spec_slug": "p2-meeting-management", - "app": "decidesk", - "repo": "https://github.com/ConductionNL/decidiq", + "app": "decidiq", + "repo": "ConductionNL/decidiq", "issue": 15, - "depends_on": ["p1-schemas-and-data-model"], + "spec_slug": "p2-meeting-management", "cycles": [ { - "cycle": 1, - "trigger": "build:queued", - "outcome": "needs-input", // ← the headline - "outcome_reason": "phpcs failures persisted after reviewer fixes", - "pattern_tags": ["reviewer-skipped-full-suite", // ← recurring-pattern labels - "phpcs-missing-phpdoc-tags"], + "cycle": 3, + "outcome": "failed", // ← the headline: passed | failed + "outcome_reason": "build: 2 gate(s) failed", + "started_at": "", "stages": [ { - "stage": "build", // build | code-review | security-review | applier - "persona": "Al Gorithm", - "model": "sonnet", - "turns_used": 37, "turns_budget": 40, // == budget → turn-budget-exhausted - "findings": [ - { - "id": "b1", "severity": "CRITICAL", - "gate": "spdx-headers", - "file": "lib/Service/MeetingService.php", "line": 1, - "status": "fixed_in_stage" // fixed_in_stage | open | fixed_later | wontfix - } - ], - "verdict": "pass" // pass | fail | none + "stage": "build", + "verdict": "fail", + "failures": 2, // number of failed gates + "exit_code": 2, + "output_tail": "…the gate output…", // what the gates actually said + "owner": "", + "credential_owner": "", + "credential_name": "" } ] } - ], - "totals": { "cycles": 5, "turns_used": 2417, "cost_usd": 1.82 } + ] } ``` -Read it top-down: the **last** `cycles[]` entry's `outcome` + `outcome_reason` tell you *what* stopped the pipeline; its `pattern_tags` tell you *whether it's a known recurring shape* (worth a retrospective, not just a retry); and the `stages[].findings[].status` fields tell you which findings were `open` (still broken) versus `fixed_in_stage` (already handled). A finding that's `open` on the last cycle is what a `retry:queued` will hand the builder via `feedback.md`. +Read it top-down: the **last** `cycles[]` entry's `outcome` and `outcome_reason` tell you *what* happened, and `output_tail` tells you *which* gates said so. Two caveats. The record step currently names every stage `build`, review stages included, so don't read the `stage` field as proof of which stage ran. And a record write that fails does not stop the run, so a missing entry does not mean the stage didn't run; check the labels and the flow runs. - - A few common pattern_tags and what they mean: turn-budget-exhausted (a stage hit turns_used == turns_budget — the work was too big for the budget), recheck-caught-new-findings (a reviewer's own fix broke a gate on the post-fix recheck), reviewer-skipped-full-suite (a reviewer ran only a subset of the required gates), label-state-contradiction (both :pass and :fail present), container-crashed-mid-stage (a stage died before persisting). The first three are the ones you'll see most. + + Records written by the shell pipeline before the move to flows carry more: findings[] with a status per finding, pattern_tags such as turn-budget-exhausted or reviewer-skipped-full-suite, depends_on, turn counts. The flows do not write those fields. When you open an older hydra.json, that is why it looks different. ## When is "human takes over" simply the right call? -Not every `needs-input` issue deserves an automated fix path. Sometimes the most pragmatic action is: **human takes over, fixes it in a commit on the feature branch, pushes, marks resolved**. +Not every stuck issue deserves an automated path. Sometimes the most pragmatic action is: **a human takes over, fixes it in a commit on the feature branch, pushes, and queues the review**. Examples: -- A small semantic decision the Builder can't make without context outside the change (e.g. "should this field be optional or required?"). +- A small semantic decision the builder can't make without context outside the change (e.g. "should this field be optional or required?"). - An interaction with an external party (open ticket with a vendor). -- A gate that is a false positive and requires a fix in `hydra/` itself (which you won't resolve within one retry cycle). +- A gate that is a false positive and requires a fix in the gate package itself (which you won't resolve within one build). Hydra is a factory, not an ideology. Take the human shortcut when it's the fairer call. @@ -239,13 +232,14 @@ One dimension: cost. The other: how much already-done work you lose. From **cheap to expensive**, with the goal of preserving as much already-done work as possible: -1. **`gh pr update-branch`** — free. All build/review/applier work stays, just refresh dependencies. -2. **Reset reviewer labels** — costs 2× reviewer + applier. Builder output stays. -3. **`retry:queued`** — costs 1× builder + 2× reviewer + applier. Branch + PR stay; builder fixes scoped via `feedback.md`. -4. **`rebuild:queued`** — full cycle. PR closes, branch resets, from scratch. -5. **Manual fix** — when the loop hangs on a false-positive gate or a human needs to make a semantic decision. +1. **`gh pr update-branch`** — free. Everything stays; only the base is refreshed. +2. **Add `code-review:queued`** after a `build:pass` — nothing queues it for you. +3. **Re-queue a review stage** — remove its verdict, add `:queued`. The build stays. +4. **Unblock a `needs-input` stage** — remove its `:queued` first, fix the cause, then queue it again. +5. **Re-queue the build** — remove the `build:*` verdict, add `build:queued`. A full new build from the base branch. +6. **Manual fix** — when the same gate keeps failing, or a human needs to make a semantic decision. -Reach for `rebuild:queued` only if the original build was fundamentally wrong; otherwise you waste good work. +Reach for a new build only if the original build was wrong; otherwise you waste good work. `retry:queued` and `rebuild:queued` are not on the list: no flow reads them. @@ -253,18 +247,20 @@ Reach for `rebuild:queued` only if the original build was fundamentally wrong; o
-**2. What's the difference between "reset reviewer labels" and `retry:queued`?** +**2. What's the difference between `needs-input` and a `:fail` on a review stage?**
-The question is: **is the builder called again, yes or no?** +The question is: **did the reviewer actually deliver a verdict?**
-- **Reset reviewer labels** (strip stage labels + set `code-review:queued` again): **the builder is NOT called again**. PR + builder output stay exactly as they were; only Juan and Clyde run again. Appropriate when the **review contracts** were adjusted (new ADR, new gate skill) and you want to re-judge without changing the code. -- **`retry:queued`**: **the builder IS called again**, in `HYDRA_MODE=fix`, with `feedback.md` (from `hydra.json`) as input. It makes new commits, then reviewers run again. Appropriate when reviewers had **legitimate findings** and the builder just needs to polish. +- **`:fail`**: the reviewer ran, produced a readable verdict with `hydra-gates` in `checks_run`, and said no. That is a judgement on the code. Nothing further is queued; you fix the code (or disagree with the verdict) and re-queue the stage. +- **`needs-input`**: the stage produced **no usable verdict** — the verdict file was missing or unreadable, `checks_run` did not name `hydra-gates`, or the stage could not run. That says nothing about the code. The stage also keeps its `:queued` label, so it runs again every tick until you remove it. + +The build lane never sets `needs-input`; its outcomes are `build:pass`, `build:fail`, `build:no-change` and `build:blocked`.
@@ -272,24 +268,24 @@ The question is: **is the builder called again, yes or no?**
-**3. When does it make more sense to do the fix by hand instead of another `retry:queued`?** +**3. When does it make more sense to do the fix by hand instead of another build?**
-If the same pattern keeps repeating on the same mechanical gate, throwing more money at it is not the answer. +If the same pattern keeps repeating on the same mechanical gate, throwing more model time at it is not the answer.
-When the same issue has gone to `needs-input` **3 times or more** for the same reason — typically a mechanical gate like `spdx-headers: fail`. Each `retry:queued` reproduces the same pattern: builder makes the same mistake, reviewer sees it as boilerplate, recheck goes red. +When the same issue has failed **3 times or more** for the same reason — typically a mechanical gate like the SPDX header check. Each new build reproduces the same pattern. In that case: -- First check whether it's a **false-positive gate** (see part 3). If so: tighten gate detection. -- If not: **fix it by hand** (e.g. commit SPDX headers yourself on the feature branch) and use a label reset to have the reviewers judge again. +- First check whether it's a **false-positive gate** (see part 3). If so: fix the gate. +- If not: **fix it by hand** (e.g. commit the SPDX headers yourself on `feature//hydra-build`) and queue the code review. -Time: minutes. Cheaper than wasting another Sonnet cycle on something that keeps going wrong. +Time: minutes. Cheaper than another model run on something that keeps going wrong.
@@ -297,19 +293,19 @@ Time: minutes. Cheaper than wasting another Sonnet cycle on something that keeps
-**4. What do you do if you see 3+ identical `needs-input` comments within a short period?** +**4. The same review stage runs again every five minutes on one issue. What is going on, and what do you do?**
-This says something about the pipeline itself, not about one issue. What kind of fix is that? +What does the sequencer search for, and what stays on the issue when a stage ends blocked?
-**This is an infra bug, not a retry question.** Somewhere in the completion handler a terminal-state guard is missing, causing a tight loop to post a new escalation comment every tick on an already-terminal issue. Example: 134 duplicate comments overnight on 23 April 2026 — the fix was a terminal-state guard before all side-effects (see CLAUDE.md section "Terminal-state guards"). +The stage ended in `needs-input` and **kept its `:queued` label**. The sequencer only searches for that label, and it has no terminal-state guard that skips `needs-input` issues, so it picks the stage up again on every tick. -**What you do:** open an **infra issue** in `hydra/`, not another `retry:queued`. A retry does nothing about the comment storm and may even add more chaos. +**What you do:** remove the stage's `:queued` label first, so the re-runs stop. Then find the cause (no verdict file, gates not run, stage could not start), fix it, remove `needs-input` and queue the stage again. Re-queueing without removing the label first changes nothing; a missing guard is an infra problem, worth an issue in `hydra/`.
@@ -324,7 +320,7 @@ This says something about the pipeline itself, not about one issue. What kind of diff --git a/academy/2026-06-11-hydra-tutorial-7-ci-and-deployment/index.mdx b/academy/2026-06-11-hydra-tutorial-7-ci-and-deployment/index.mdx index 19ccdfdb9..84d298c53 100644 --- a/academy/2026-06-11-hydra-tutorial-7-ci-and-deployment/index.mdx +++ b/academy/2026-06-11-hydra-tutorial-7-ci-and-deployment/index.mdx @@ -5,7 +5,7 @@ contentType: tutorial heroIcon: pipeline authors: [conduction] date: 2026-06-11 -summary: "Parts 5 and 6 ran Hydra on your own machine. This part zooms out to how Hydra runs unattended: flows that notice a labelled issue and start each stage, container images that GitHub Actions builds and pushes to GHCR, and brokered credentials with an owner for every run. It lays out the three deployment models and marks honestly what is documented and what is infrastructure-only." +summary: "Parts 5 and 6 ran Hydra on your own machine. This part zooms out to how Hydra runs unattended: flows that notice a labelled issue and start each stage, container images that GitHub Actions builds and pushes to GHCR, and brokered credentials with an owner named on every recorded stage. It lays out the three deployment models and marks honestly what is documented and what is infrastructure-only." tags: [Hydra, CI/CD, GitHub, Deployment, Operations, Tutorial series] apps: [] durationMinutes: 14 @@ -16,14 +16,14 @@ partNumber: 7 import {Outcomes, Outcome, Prerequisites, PrerequisiteItem, NextSteps, NextStep, HexCard, Troubleshooting, TroubleshootingItem, ContactCta} from '@conduction/docusaurus-preset/components'; import Details from '@theme/Details'; -In [part 5](/academy/hydra-tutorial-5-starting-a-run) you ran Hydra by hand: build the images, run a stage with `dev-run.sh`, label an issue. That is the right way to learn Hydra, but it is not how it runs day to day. In production nobody starts containers from a terminal. Instead a scheduled flow notices the labelled issue, and each stage starts from a pre-built image pulled from a registry. This part explains that path, and the three deployment models behind it. +In [part 5](/academy/hydra-tutorial-5-starting-a-run) you ran Hydra by hand: build the images, run a stage with `dev-run.sh`, label an issue. That is the right way to learn Hydra, but it is not how it runs day to day. In production nobody starts containers from a terminal. Instead a scheduled flow notices the labelled issue and has each stage run for it, with no one at the keyboard. This part explains that path, where the pre-built images fit in, and the three deployment models behind it. {/* truncate */} - How a labelled issue starts Hydra through the hydra-dispatch and hydra-sequencer flows, with no human at a terminal, and why that is no longer a GitHub Actions job. + How a labelled issue starts Hydra through the hydra-sequencer flow, with no human at a terminal, and why that is no longer a GitHub Actions job. Where the container images come from (the GitHub Container Registry, ghcr.io/conductionnl/hydra-*) and how the image tag is configured. - The credential model: a brokered forge credential that hydra only knows by id, and an owner for every run, so a personal subscription only serves its owner. + The credential model: a brokered forge credential that hydra only knows by id, and an owner named on every recorded stage. The three deployment models (local, GitHub Actions, Kubernetes) and which parts are documented vs infrastructure-only. @@ -35,27 +35,31 @@ In [part 5](/academy/hydra-tutorial-5-starting-a-run) you ran Hydra by hand: bui ## Same pipeline, different trigger -Nothing about the pipeline changes between local and CI. The personas, the labels, the gates, the no-loop rule from part 2: all identical. What changes is who pulls the trigger and where the work runs: +The personas, the gates and the no-loop rule from part 2 are the same locally and in production. What differs is who pulls the trigger, where the work runs, and what gets recorded: | | Local (part 5) | CI / production (this part) | |---|---|---| -| Trigger | you run `dev-run.sh` or `smoke-test.sh` | the `hydra-dispatch` flow finds the label on its schedule | -| Where stages run | containers on your machine | the hermiq ExApp, started by OpenRegister flows | -| Images | you `docker build` them | pulled from GHCR (`ghcr.io/conductionnl/hydra-*`) | -| Credentials | `GH_TOKEN` and `CLAUDE_CODE_OAUTH_TOKEN` in your shell | a brokered credential id; the secret stays in the broker | +| Trigger | you run `dev-run.sh` or `smoke-test.sh` | the `hydra-sequencer` flow finds a queued label on its five-minute schedule | +| Where stages run | containers on your machine | the hermiq ExApp, as workload steps started by OpenRegister flows | +| Images | you `docker build` them | the workload steps name no image; the GHCR images (`ghcr.io/conductionnl/hydra-*`) serve the container paths: scripts on a host and the Kubernetes Jobs | +| Credentials | `GH_TOKEN` and `CLAUDE_CODE_OAUTH_TOKEN` in your shell | brokered credential ids, separate ones for reading, pushing and the model; the secrets stay in the broker | +| Record | none | labels move, a PR is opened, and each stage is written to `hydra.json` | -So everything you learned about labels and the state machine still applies. You are just handing the steering wheel to the flows. +So everything you learned about labels in parts 5 and 6 applies. You are just handing the steering wheel to the flows. ## Trigger: flows, not Actions -Hydra's stage dispatch does not run in CI. It briefly did: [hydra#448](https://github.com/ConductionNL/hydra/pull/448) (2026-07-31) ran the stage as an Actions job, `hydra-stage.yml`, and [hydra#452](https://github.com/ConductionNL/hydra/pull/452) (2026-08-01) removed it again. (The older `hydra-build.yml` and `hydra-review.yml` had already been retired in May.) The people Hydra is built for have Nextcloud and LLM access on a Kubernetes environment someone else set up, and no forge account with Actions rights or cluster access of their own. So the stage now runs on what they already have: Nextcloud's job system, OpenRegister flows and the **hermiq ExApp**. Two flows in hydra's `flows/` folder, run by OpenRegister, decide what runs next: +Hydra's stage dispatch does not run in CI. It briefly did: [hydra#448](https://github.com/ConductionNL/hydra/pull/448) (2026-07-31) ran the stage as an Actions job, `hydra-stage.yml`, and [hydra#452](https://github.com/ConductionNL/hydra/pull/452) (2026-08-01) removed it again. (The older `hydra-build.yml` and `hydra-review.yml` had already been retired in May.) The people Hydra is built for have Nextcloud and LLM access on a Kubernetes environment someone else set up, and no forge account with Actions rights or cluster access of their own. So the stage now runs on what they already have: Nextcloud, OpenRegister flows and the **hermiq ExApp**, which runs each stage as `claude -p` in a checkout of the target repo. The flows live in hydra's `flows/` folder: -- **`hydra-dispatch`** runs on a schedule (every five minutes), polls the forge for queued work and starts at most a fixed number of pipelines at once. -- **`hydra-sequencer`** moves an issue from stage to stage, and runs the dependency and sibling checks before it dispatches a spec. It searches the whole `ConductionNL` organisation in one call rather than walking the repos one by one. +- **`hydra-sequencer`** is the pipeline. It runs every five minutes and does one unit of work per tick: it resumes a review stage that is in flight, or else takes the first open issue in the `ConductionNL` organisation labelled `applier:queued`, then `security-review:queued`, then `code-review:queued`, then `build:queued`. It searches the whole organisation in one call rather than walking the repos one by one, and claims a slot from a shared pool of three before it starts work. +- **`hydra-dispatch`** claims a slot and runs one hermiq agent step for an issue it is handed. It does not search the forge and does not hand work to the sequencer. +- **`hydra-lock-reaper`** runs every ten minutes and clears stale per-issue locks. + +Both `hydra-sequencer` and `hydra-dispatch` ship with `enabled: false`, so nothing runs until someone switches them on in the OpenRegister instance. And the flows check less than older docs claimed: no flow enforces a spec's `depends_on` list or keeps sibling specs from building at the same time. What GitHub Actions still does is build the images, in **`.github/workflows/hydra-image.yml`**, on every push to `main` or `development`. Nothing else can build a container image, so that is the one CI job left. -The entry point is unchanged from part 1: a label on the tracking issue, not a person at a terminal. Same contract, different plumbing. +The entry point is unchanged from part 1: a label on the issue (`build:queued`), not a person at a terminal. Same contract, different plumbing. Hydra is built forge-agnostic. The HYDRA_FORGE variable selects the REST adapter: github (scripts/lib/github-rest.sh) for every ConductionNL repo, or forgejo (codeberg is accepted as an alias, scripts/lib/forgejo-rest.sh) for a customer who runs a self-hosted Forgejo. There is no default: scripts/lib/forge-api.sh refuses to start when the variable is unset, because guessing a forge means sending the run's credentials to a third party. It used to default to codeberg, and that is how a GitHub token once ended up at codeberg.org. If older docs show codeberg.org/Conduction/<app> URLs, that is the 2026-05-29 move to Codeberg showing. It was reversed (directive 2026-07-17, executed 2026-07-23), and GitHub is the only host for ConductionNL work. @@ -63,19 +67,21 @@ The entry point is unchanged from part 1: a label on the tracking issue, not a p ## Images: the GitHub Container Registry -In a local run you build the images yourself (part 5). For unattended runs that would waste minutes on every stage, so `hydra-image.yml` builds them once and pushes them to the **GitHub Container Registry** (GHCR): the `hydra-nextcloud-test:stable32` base image first, `hydra-builder` on top of it, and `hydra-reviewer` and `hydra-security` alongside. The naming is driven by two variables in `secrets/.env`: +In a local run you build the images yourself (part 5). Wherever a stage runs as a container outside your laptop, that would waste minutes on every stage, so `hydra-image.yml` builds them once and pushes them to the **GitHub Container Registry** (GHCR): the `hydra-nextcloud-test:stable32` base image first, then `hydra-builder` (which copies the Nextcloud tree out of that base), and `hydra-reviewer` and `hydra-security` alongside. The Kubernetes Jobs name these images directly. For the scripts on a host (`manual-review.sh`, the cron scripts), the naming is driven by two variables in `secrets/.env`: ```bash -# production values; part 5 used localhost/hydra and test for local builds +# pull the published images; unset, the scripts use localhost/hydra and test (part 5) HYDRA_IMAGE_PREFIX=ghcr.io/conductionnl/hydra HYDRA_IMAGE_TAG=latest ``` +`dev-run.sh` ignores both and always starts `localhost/hydra-:test`. + So the reviewer image resolves to `ghcr.io/conductionnl/hydra-reviewer:latest`, and the same for builder and security. (The applier image is not built by this workflow.) `latest` only moves when a release is published. Every push to `main` or `development` also publishes an immutable `sha-` tag, and pinning `HYDRA_IMAGE_TAG` to one of those is how you freeze a known-good pipeline version instead of always tracking `latest`. ## Credentials: a reference, not a token -Production Hydra never uses a shared org-admin token, and it never holds a forge token at all. Every forge call the flows make, whether for an issue, a label, a comment or a pull request, goes through one OpenConnector source, `flows/hydra-forge.source.json`. That source carries only a `credentialRef`: the id of a credential that lives in the broker (Doriath). The broker fills in the secret and sends the call, locked to `api.github.com`. What hydra stores is a uuid: +Production Hydra never uses a shared org-admin token, and it never holds a forge token at all. Every issue, label, comment and pull-request call the flows make goes through one OpenConnector source, `flows/hydra-forge.source.json`. That source carries only a `credentialRef`: the id of a credential that lives in the broker (Doriath). The broker fills in the secret and sends the call, locked to `api.github.com`. What hydra stores is a uuid: ```json "authentication": { @@ -83,14 +89,14 @@ Production Hydra never uses a shared org-admin token, and it never holds a forge } ``` -When the reference cannot be resolved, the run is refused. Nothing falls back to another token. +When the reference cannot be resolved, the run is refused. Nothing falls back to another token. The workload steps work the same way: they carry separate credential ids for reading the repo, for pushing, and for the model, and the push is done by the pipeline, so the agent that writes the code never holds a forge credential. -The Claude side follows the personal-scope rule from part 5: +The Claude side follows the personal-scope rule from part 5: a Claude Max or Pro subscription may serve its owner, not an organisation. Hydra's part in that is making every stage say whose credential ran it: -1. **Every run has an owner.** It is resolved once per cycle: the issue's `owner:` marker, or else the person who applied the label. A bot is never the owner. -2. **A subscription only serves its owner.** A Claude Max or Pro credential runs only its owner's stages. A run without a resolvable owner may only be served by an organisation API key. The workload step refuses a stage it cannot attribute, and `hydra-record-stage` writes no record without a `credential_owner`, so an unattributed stage can never advance its label. +1. **The owner comes from the step that ran the stage.** The hermiq workload step reports the stage's `owner`, together with `credential_owner` and `credential_name`. How hermiq resolves the owner is configured in hermiq, not in the hydra repo. +2. **No owner, no record.** `hydra-record-stage` refuses to record a stage without an owner, because "who ran this, on whose credential" cannot be recovered after the run. -`hydra.json` records whose credential ran each stage (`cycles[].owner`, `stages[].credential_owner`). It never holds the token itself. +`hydra.json` records those three fields on every stage entry. It never holds the token itself. :::note What changed Until 2026 this section described `secrets/credentials.json`: a list of Claude tokens with automatic rotation on rate-limit, plus one git token per persona. The file, the token pool and the rotation were retired with the move to flows. @@ -105,11 +111,11 @@ Hydra's `docs/deployment-models.md` describes three ways to run it, in increasin - Until hydra#452 (2026-08-01) the build and review stages ran as Actions jobs, triggered by issue labels and PR events, with credentials in org secrets. That dispatch was removed: the stage now runs in the hermiq ExApp, started by the OpenRegister flows above. What stays in GitHub Actions is the image build, hydra-image.yml, which every other model pulls from. + Until hydra#452 (2026-08-01) the build and review stages ran as Actions jobs, triggered by issue labels and PR events, with credentials in org secrets. That dispatch was removed: the stage now runs in the hermiq ExApp, started by the OpenRegister flows above. What stays in GitHub Actions is the image build, hydra-image.yml, which the container paths (scripts on a host, Model 3) pull from. - Each stage runs as a Kubernetes Job (manifests/builder-job.yaml, reviewer-job.yaml, security-job.yaml), with a NetworkPolicy and an ArgoCD Application that syncs manifests/ from main. For scale and isolation. + Each stage runs as a Kubernetes Job (manifests/builder-job.yaml, reviewer-job.yaml, security-job.yaml), with a NetworkPolicy and an ArgoCD Application that syncs manifests/ from main. For scale and isolation. There is also an applier-job.yaml, but hydra-image.yml does not build the applier image it names. @@ -120,13 +126,13 @@ Hydra's `docs/deployment-models.md` describes three ways to run it, in increasin - Unattended dispatch is the hydra-dispatch flow, not an Actions workflow, so there is no workflow run to look for. Its definition ships with enabled: false: check that the flow is activated on the OpenRegister instance, then give it one five-minute tick. + Unattended dispatch is the hydra-sequencer flow, not an Actions workflow, so there is no workflow run to look for. Its definition ships with enabled: false: check that the flow is activated on the OpenRegister instance, then give it a few five-minute ticks. It does one unit of work per tick, review stages before builds, so a busy queue takes longer. The label has to be the bare build:queued; a prefixed label is never picked up. - - The tag is wrong or the image was never built. Confirm hydra-image.yml ran on GitHub Actions and pushed ghcr.io/conductionnl/hydra-<role> with the expected tag, and that the host has pull access to GHCR. A codeberg.org/conduction/hydra-* reference is stale configuration: no Hydra image was ever published there. + + This applies to the container paths (the scripts on a host, the Kubernetes Jobs); the flows' workload steps name no image. The tag is wrong or the image was never built. Confirm hydra-image.yml ran on GitHub Actions and pushed ghcr.io/conductionnl/hydra-<role> with the expected tag, and that the host has pull access to GHCR. A codeberg.org/conduction/hydra-* reference is stale configuration: no Hydra image was ever published there. - - The run has no owner that can be resolved. Put an owner:<uid> marker on the issue, or have a person (not a bot) apply the label. Only an organisation API key may run a stage without an owner. + + hydra-record-stage refused it as unattributed ("A stage result with no owner cannot be recorded"), or the record write failed. Neither stops the run: the labels and the PR are already there. For the first, check how the owner is resolved for that workload step in the hermiq configuration of your instance; the record flow only checks that one is present. The credentialId in hydra-forge.source.json is instance-specific. When you apply the source, substitute the id of the credential in your own OpenRegister instance. @@ -147,9 +153,9 @@ Think about the trigger and the runtime versus the pipeline itself.
-What changes: the trigger (the scheduled `hydra-dispatch` flow finding the label instead of you starting a container), where stages run (the hermiq ExApp instead of containers on your machine), where images come from (a GHCR pull instead of a local `docker build`), and where credentials live (a brokered credential id instead of variables in your shell). +What changes: the trigger (the scheduled `hydra-sequencer` flow finding the label instead of you starting a container), where stages run (hermiq workload steps instead of containers on your machine, so no image is pulled for them), where credentials live (brokered credential ids instead of variables in your shell), and what is recorded (labels, a PR and `hydra.json` entries, where a local run leaves no record). -What stays the same: the entire pipeline. Personas, labels, the state machine, the 22 gates, the no-loop rule. The contract is still one tracking issue labelled `ready-to-build`. +What stays the same: the personas, the gate suite (whose run prints its own COVERAGE line rather than a fixed count), and the no-loop rule. The contract is one issue labelled `build:queued`.
@@ -195,7 +201,7 @@ Model 2 (GitHub Actions) has not run stages since hydra#452 (2026-08-01); it onl Wat een Claude-Code-skill is en hoe Hydra ze gebruikt om gedrag bij personas te bundelen. - Welke skills de automatische Hydra-fabriek in zijn Docker-containers heeft staan (per agent: Builder, Reviewer, Security) — en welke skills alleen voor mensen op de CLI zijn. + Wat een stage in de automatische Hydra-fabriek te gebruiken krijgt, welke skills de container-images meedragen (Builder, Reviewer, Security) — en welke skills alleen voor mensen op de CLI zijn. De vijf skill-families binnen Hydra: {'opsx-*'}, {'hydra-gate-*'}, {'team-*'}, {'test-*'}, en de utility/maintenance-skills. De twee manieren waarop een skill geactiveerd kan worden — handmatig via /{''} of automatisch door Claude — en hoe je dat per skill stuurt. Wanneer je zelf een skill toevoegt en hoe je dat structureel doet. @@ -55,40 +55,44 @@ Welke mode is toegestaan, regel je in de frontmatter van de skill zelf: - `user-invocable: false` — alleen Claude zelf mag 'm laden. Gebruik dit voor achtergrondkennis (bijvoorbeeld een `legacy-system-context` die niet als actie zinvol is). - Geen van beide gezet — beide mogen. -Dat onderscheid — handmatig vs. automatisch — gaat dus over **configuratie van één skill**, niet over twee verschillende soorten bestanden. Een persona in een Docker-container heeft geen toetsenbord en leunt vooral op auto-activatie; een mens op de CLI wil meestal expliciete controle en typt `/`. +Dat onderscheid — handmatig vs. automatisch — gaat dus over **configuratie van één skill**, niet over twee verschillende soorten bestanden. Een pipeline-stage heeft geen toetsenbord: hij krijgt een prompt die zegt wat hij moet doen en welke brief hij volgt, en wat hij daarnaast laadt is Claude's eigen keuze. Een mens op de CLI wil meestal expliciete controle en typt `/`. ## Twee werelden: de Hydra-fabriek vs. skills voor mensen -Hydra's `hydra/.claude/skills/` bevat ~70 skills, maar de **automatische pipeline gebruikt er maar een handvol**. De rest is gereedschap voor jou, achter een toetsenbord. Belangrijk om uit elkaar te houden: +Hydra's `hydra/.claude/skills/` bevat zo'n 130 skill-mapjes, waarvan ongeveer 60 `hydra-gate-*` (`ls .claude/skills` geeft het aantal van vandaag). **De automatische pipeline leunt op heel weinig daarvan.** De rest is gereedschap voor jou, achter een toetsenbord. Belangrijk om uit elkaar te houden: ### Wat de Hydra-fabriek echt draait -Elke pipeline-container heeft een eigen, beperkte set skills in zijn Docker-image gebakken: +Een echte run wordt gedreven door de flow `hydra-sequencer` (deel 7). De hermiq-app draait elke stage als `claude -p` in een checkout van de doel-repo, met Hydra's eigen tool-tree ernaast. De prompt van de stage zegt wat er moet gebeuren: + +- **Build (Al Gorithm)** — de prompt vraagt om de OpenSpec-change uit het issue te implementeren. Hij noemt geen skill. De agent mag bestanden lezen en bewerken (Read, Edit, Write, Glob, Grep) en krijgt de opdracht geen git te draaien; de pipeline commit en pusht. Daarna draait de pipeline zelf `scripts/run-hydra-gates.sh --scope-to-diff`, staat één fix-ronde op de gate-output toe, en draait de gates opnieuw. +- **Code review, security review, applier (Juan Claude, Clyde Barcode, Axel Pliér)** — elk leest zijn eigen brief uit `agents//` plus de stage-brief onder `images/`, draait de gates zoals die brief voorschrijft, en schrijft een verdict-bestand. Een verdict waarvan `checks_run` geen `hydra-gates` noemt, wordt geweigerd. + +De **container-images** start je zelf, met `dev-run.sh`, `smoke-test.sh` of `manual-review.sh` (deel 5). Elk heeft een eigen, beperkte set skills ingebakken: | Container | Persona | Skills in image | Hoofdfunctie | |---|---|---|---| -| **Builder** | Al Gorithm | **Alle** `.claude/skills/*` + alle vendor skills | Implementeert taken; primair `opsx-apply`. Heeft de overige opsx-skills bij de hand voor context bij verifiëren, hertrouw zoeken, fixen na quality-fail. | -| **Reviewer** | Juan Claude | `hydra-gates` + 13 `hydra-gate-*` skills + vendor `code-review` | Mandatory hydra-gates check + diepere code-review via de Anthropic community-skill. | -| **Security** | Clyde Barcode | `hydra-gates` + 13 `hydra-gate-*` skills + vendor `trailofbits` + vendor `owasp` | Mandatory hydra-gates + SAST (Semgrep) + OWASP-top-10-checklists. | -| **Applier** | Axel Plier | **Geen skills** | Past kleine, deterministische fixes toe puur via zijn `CLAUDE.md` — geen skill-laag nodig. | -| **Browser UI Tester** | (sonnet, headless) | Eén skill: `hydra-ui-test` | Logt in, navigeert de live app via Playwright MCP, levert verdict JSON. | +| **Builder** | Al Gorithm | **Alle** skills uit `.claude/skills/` (geen vendor skills) | Implementeert de change; de entrypoint van de image volgt `opsx-apply`. De overige opsx-skills zijn er voor context. | +| **Reviewer** | Juan Claude | `hydra-gates` + een vaste selectie van 16 `hydra-gate-*` skills + vendor `code-review` | Verplichte hydra-gates-check + diepere code-review via de Anthropic community-skill. | +| **Security** | Clyde Barcode | `hydra-gates` + dezelfde 16 `hydra-gate-*` skills + vendor `trailofbits` (de Semgrep-plugin) + vendor `owasp` | Verplichte hydra-gates + SAST (Semgrep) + OWASP-top-10-checklists. | +| **Applier** | Axel Pliér | **Geen skills**; zijn config staat alleen Read en Bash toe | Leest de uiteindelijke diff en beide review-verdicts en geeft één binair antwoord — `pass` of `fail`. Hij past **geen** fixes toe; hij is een go/no-go-poort, geen editor. | -Daarnaast draait **`scripts/run-hydra-gates.sh`** in elke container alle **14** hydra-gates mechanisch — los van welke skill-files er in de image staan. De skill-files dienen voor Claude als documentatie bij een fix; het script doet de detectie. +Welke skill-files een image ook meedraagt, de detectie doet **`scripts/run-hydra-gates.sh`**. Dat is een dun doorgeefluik naar het package `conduction/hydra-gates`, dat vandaag zo'n 96 gates declareert. Dat getal groeit met elk incident, dus vertrouw geen aantal dat in een doc staat (deze inbegrepen): elke run print een COVERAGE-regel met wat hij declareerde en wat er echt draaide. De skill-files dienen Claude als documentatie bij een fix; het script doet de detectie. ### Wat de fabriek **niet** doet — voor mensen op de CLI -Alle andere skills (~50 van de 70) zijn voor jou, of voor een collega-dev in een Claude Code sessie op zijn laptop. Voorbeelden: +Vrijwel alle andere skills zijn voor jou, of voor een collega-dev in een Claude Code-sessie op zijn laptop. Voorbeelden: -- **Een change voorbereiden** — `opsx-new`, `opsx-ff`, `opsx-explore`, `opsx-plan-to-issues` doe je als mens vóór je het werk in de pipeline gooit. De Builder runt alleen `opsx-apply` op een al-bestaande change. -- **Een role aannemen** — `team-architect`, `team-backend`, `team-po` enz. zijn pure roleplay-frames voor één-mens-één-rol sessies. De pipeline gebruikt ze niet. -- **Testen draaien** — `/test-counsel` (alle 8 personas) of `/test-app` (één browser-sweep) start je met de hand. De fabriek heeft een eigen browser-tester (`hydra-ui-test`); de `test-*` familie staat los daarvan. -- **PR's en dag-werk** — `/create-pr`, `/review-pr`, `/report-out` zijn de "drie keer per dag"-tools voor jou, niet voor de pipeline. +- **Een change voorbereiden** — `opsx-new`, `opsx-ff`, `opsx-explore`, `opsx-plan-to-issues` doe je als mens vóór je het werk in de pipeline gooit. De pipeline bouwt alleen een change die al bestaat. +- **Een rol aannemen** — `team-architect`, `team-backend`, `team-po` enz. zijn pure roleplay-frames voor één-mens-één-rol-sessies. De pipeline gebruikt ze niet. +- **Testen draaien** — `/test-counsel` (alle negen personas) of `/test-app` (één browser-sweep) start je met de hand. De pipeline draait helemaal geen browser; de `test-*`-familie is van jou. +- **PR's en dagelijks werk** — `/create-pr`, `/review-pr`, `/report-out` zijn de "drie keer per dag"-tools voor jou, niet voor de pipeline. -Onthoud: **de fabriek pakt 5 skills, een mens kan tegen alle 70 aanroepen**. Dat is het hele verschil. +Onthoud: **een pipeline-stage volgt zijn prompt, zijn brief en de gate-suite; een mens kan elke skill in de map aanroepen**. Dat is het hele verschil. ## De vijf skill-families compleet -Met die scheiding in het achterhoofd, hier zijn alle vijf families. Het tag-systeem in de tabel: **🤖 fabriek** = staat in een pipeline-container, **⌨️ mens** = alleen via CLI. +Met die scheiding in het achterhoofd, hier zijn alle vijf families. Het tag-systeem in de tabel: **🤖 fabriek** = gebruikt door de pipeline of door de entrypoint van een pipeline-image, **⌨️ mens** = roep je zelf aan. (De builder-image kopieert de hele map `.claude/skills/`, dus de meeste ⌨️-skills *staan* erin; niets in de pipeline roept ze aan.) ### Familie 1: OpenSpec workflow (`opsx-*`, 16 skills) @@ -96,109 +100,114 @@ De OPSX-skills implementeren de Conduction-workflow voor OpenSpec-changes — va | Skill | Voor wie | Doet | |---|---|---| -| `opsx-apply` | 🤖 Builder | Implementeert taken uit een change (de pipeline-default). | -| `opsx-verify` | 🤖 Builder | Controleert dat de implementatie de change-artefacten dekt. | -| `opsx-archive` | 🤖 Builder + ⌨️ | Archiveert een afgeronde change, syncing delta naar de spec. | -| `opsx-new` | ⌨️ | Start een nieuwe change (proposal scaffold, schema-keuze). | +| `opsx-apply` | 🤖 Builder-image + ⌨️ | Implementeert taken uit een change. De entrypoint van de builder-image volgt hem; de build-prompt van de flow noemt hem niet. | +| `opsx-verify` | ⌨️ | Controleert dat de implementatie de change-artefacten dekt. | +| `opsx-archive` | ⌨️ | Archiveert een afgeronde change en synct de delta naar de spec. | +| `opsx-new` | ⌨️ | Start een nieuwe change (proposal-scaffold, schema-keuze). | | `opsx-ff` | ⌨️ | "Fast-forward": maakt change + alle artefacten in één pass. | | `opsx-continue` | ⌨️ | Pak een onderbroken change op. | | `opsx-explore` | ⌨️ | Verken pre-spec wat een change überhaupt zou moeten zijn. | -| `opsx-onboard` | ⌨️ | Onboarding-flow op een nieuwe repo (zet OpenSpec config + structuur op). | -| `opsx-plan-to-issues` | ⌨️ | Converteert `tasks.md` naar GitHub-issues + tracking-issue. | +| `opsx-onboard` | ⌨️ | Begeleide rondgang door één complete OpenSpec-workflowcyclus, met uitleg onderweg. | +| `opsx-plan-to-issues` | ⌨️ | Zet `tasks.md` om naar een `plan.json` en een tracking-issue met taak-checkboxes. | | `opsx-sync` | ⌨️ | Sync delta-specs terug naar de hoofdspec. | | `opsx-bulk-archive` | ⌨️ | Archiveer meerdere afgeronde changes in één keer. | -| `opsx-apply-loop` | ⌨️ | Headless build → quality-fix lus voor lokale dev-runs. | -| `opsx-pipeline` | ⌨️ | Parallel meerdere changes tegelijk laten lopen (multi-agent). | +| `opsx-apply-loop` | ⌨️ | Draait apply → verify in een lus tot verify slaagt, en archiveert dan; per app, in Docker. | +| `opsx-pipeline` | ⌨️ | Meerdere changes parallel laten lopen met subagents. | | `opsx-coverage-scan` | ⌨️ | Audit een legacy app op spec ↔ code coverage. | | `opsx-annotate` | ⌨️ | Past `@spec` PHPDoc-tags toe na een coverage-scan. | | `opsx-reverse-spec` | ⌨️ | Reverse-engineert een spec uit bestaande code. | -### Familie 2: Quality + security gates (`hydra-gate-*`, 14 skills) +Zestien skills is veel. In de praktijk pak je er een handvol, en de keuze volgt een simpele beslishulp: -De parent-skill **`hydra-gates`** is een dispatcher die alle gates samen afroept. De script-engine onder de motorkap (`scripts/run-hydra-gates.sh`) draait alle 14 gates in elke container; de skill-files dienen Claude als referentie bij het fixen van een failure. +- **Nieuw begonnen en zeker van de feature?** → `opsx-ff` (in één pass naar alle artefacten), daarna `opsx-plan-to-issues`. +- **Nieuw begonnen maar onzeker over scope of aanpak?** → eerst `opsx-explore`, dan stap voor stap `opsx-new` en `opsx-continue`. +- **Nieuw met de workflow?** → `opsx-onboard` neemt je mee door één volledige cyclus. +- **Legacy code zonder specs?** → `opsx-coverage-scan` → `opsx-reverse-spec` / `opsx-annotate`. +- **Change is geschreven, wil je hem lokaal laten bouwen zonder de hele fabriek?** → `opsx-apply-loop` (één change) of `opsx-pipeline` (meerdere parallel). +- **Binnen de fabriek** wordt geen van deze bij naam aangeroepen: de build-prompt van de flow implementeert de change direct. De opsx-skills zijn voorbereiding door mensen en lokaal gereedschap. -| Gate-skill | Wat hij detecteert | -|---|---| -| `hydra-gates` (dispatcher) | Roept alle 14 gates achter elkaar aan, levert pass/fail-overzicht. | -| `hydra-gate-spdx` | Ontbrekende `@license`/`@copyright`-headers op PHP/Vue-files. | -| `hydra-gate-forbidden-patterns` | Debug-calls (`var_dump`, `console.log`), TODO's in productiecode, hardcoded secrets. | -| `hydra-gate-stub-scan` | Stub-methods die `return null;` of niets doen — geen test, geen logica. | -| `hydra-gate-composer-audit` | `composer audit` met kwetsbaarheden in afhankelijkheden. | -| `hydra-gate-route-auth` | `routes.php` zonder auth-attribuut waar dat hoort. | -| `hydra-gate-orphan-auth` | Auth-checks in methodes die nergens aangeroepen worden (verlaten). | -| `hydra-gate-no-admin-idor` | `Application::APP_ID` ontbreekt bij admin-only methodes → IDOR-risico. | -| `hydra-gate-unsafe-auth-resolver` | `catch(\Throwable) { return null; }` rond auth-logica. | -| `hydra-gate-semantic-auth` | `#[NoAdminRequired]` op een methode die toch `requireAdmin()` aanroept. | -| `hydra-gate-admin-router` | Admin-routes die niet via de admin-router lopen. | -| `hydra-gate-initial-state` | Frontend `initialState` zonder serverside hydration. | -| `hydra-gate-modal-isolation` | Vue-modals die buiten hun container teleporteren. | -| `hydra-gate-nc-input-labels` | Nextcloud-input-components zonder gekoppeld label (a11y). | - -Alle 14 draaien **🤖 in de Hydra-fabriek** (Reviewer + Security containers; Builder voert ze óók uit als post-flight check tijdens fix-quality). +### Familie 2: Quality + security gates (`hydra-gate-*`) + +De parent-skill **`hydra-gates`** draait de hele suite via `scripts/run-hydra-gates.sh` en vat de failures samen. De gate-logica zelf zit in het package `conduction/hydra-gates`, waar dat script naar doorverwijst; de skill-files per gate dienen Claude als naslag bij het fixen van een failure. Er zijn meer gates dan gate-skills, en de nummering (vandaag tot gate-116) heeft gaten. + +In plaats van hier een tabel over te nemen: de gates worden behandeld in [deel 3: Quality gates](/nl/academy/hydra-tutorial-3-quality-gates#categorie-2-hydra-specifieke-gates). Voor de skill-familie is het genoeg te weten dat de gates grofweg in groepen uiteenvallen: + +- **Hygiëne** — `spdx`, `forbidden-patterns`, `stub-scan`, `composer-audit`, `gitignore-then-commit`. +- **Security & auth** — `route-auth`, `orphan-auth`, `no-admin-idor`, `unsafe-auth-resolver`, `semantic-auth`, `route-reachability`, `csrf-cochange`. +- **Toegankelijkheid (WCAG 2.2 AA)** — `img-alt`, `button-name`, `form-label-association`, `html-lang`, `skip-link` en meer. +- **Conduction-conventies & traceerbaarheid** — `initial-state`, `admin-router`, `nc-input-labels`, `modal-isolation`, `dashboard-antipattern`, `redundant-controller`, `notification-dialect`, `manifest-validation`, en de twee traceability-gates `spec-coverage` (gate-16) en `e2e-coverage` (gate-19). + +In een flow-run draait de pipeline de suite na de build en opnieuw na de ene fix-ronde, en elke review-stage krijgt de opdracht hem te draaien. Levert een nieuw incident een nieuwe gate op, dan komt die erbij en groeit het aantal — vertrouw dus de COVERAGE-regel van de run, niet een hard getal in een doc. ### Familie 3: Team-rollen (`team-*`, 7 skills) — **alleen ⌨️ voor mensen** Skills die een specifiek soort werk modelleren via een persona-frame. Bedoeld voor een mens die naast Hydra in een terminal werkt en even één rol wil invullen. - `team-po` (Product Owner) — schrijft user stories en acceptatiecriteria. -- `team-sm` (Scrum Master) — beheert backlog en sprint-planning artefacten. +- `team-sm` (Scrum Master) — beheert backlog en sprint-planning-artefacten. - `team-architect` — neemt architectuurbeslissingen, schrijft ADR-drafts. - `team-backend` — implementeert backend-werk (PHP, services, mappers). - `team-frontend` — implementeert frontend-werk (Vue 2, Pinia, NL Design System). - `team-reviewer` — een handmatige variant van het reviewer-werk. - `team-qa` — schrijft testcases en testplannen. -Geen van deze staat in een pipeline-container — ze hebben geen plek in de automatische loop. +De builder-image draagt ze alleen mee omdat hij de hele map kopieert; niets in de automatische loop roept ze aan. -### Familie 4: Test-suites (`test-*`, 19 skills) — bijna allemaal ⌨️ voor mensen +### Familie 4: Test-suites (`test-*`) — allemaal ⌨️ voor mensen -De grootste familie qua aantal: rond de twintig skills die agentic browser- en API-testen drijven, in drie clusters: +Na de gates de grootste familie: skills die agentic browser- en API-testen aansturen, in drie clusters: **Test-types** (één per test-soort): - `test-app` — automatische browsertest van een hele Nextcloud-app (Playwright MCP). - `test-functional` — functionele scenario's tegen geïmplementeerde features. -- `test-api` — API-checks tegen de PHP built-in server. +- `test-api` — API-checks (REST, NLGov-conventies). - `test-accessibility`, `test-performance`, `test-security`, `test-regression` — gespecialiseerde varianten. -**Personas** (`test-persona-*`) — acht Nederlandse gebruikersprofielen die elk een eigen blik op een app werpen: `annemarie`, `fatima`, `henk`, `janwillem`, `mark`, `noor`, `priya`, `sem`. Henk leest met grote letters en zoekt eenvoudige navigatie; Noor hamert op RBAC en audit-trails; Annemarie controleert NLGov/GEMMA-mapping. De persona-cards zelf liggen in [`hydra/personas/`](https://github.com/ConductionNL/hydra/tree/main/personas). +**Personas** (`test-persona-*`) — negen Nederlandse gebruikersprofielen die elk een eigen blik op een app werpen: `annemarie`, `fatima`, `henk`, `janwillem`, `jasper`, `mark`, `noor`, `priya`, `sem`. Jasper gebruikt een schermlezer als zijn primaire ingang; Henk leest met grote letters en zoekt eenvoudige navigatie; Noor hamert op RBAC en audit-trails; Annemarie controleert NLGov/GEMMA-mapping. De persona-cards zelf liggen in [`hydra/personas/`](https://github.com/ConductionNL/hydra/tree/main/personas). + +**Scenario-management** — `test-scenario-create`, `test-scenario-edit`, `test-scenario-run` schrijven, bewerken en draaien herbruikbare `TS-NNN-*.md`-scenario's per app. + +De dispatcher van deze familie is **`test-counsel`**: hij coördineert alle negen personas tegen één feature en levert een gecombineerd rapport. Zelfde patroon als `hydra-gates` voor de quality-gates-familie. -**Scenario-management** — `test-scenario-create`, `test-scenario-edit`, `test-scenario-run` schrijven, bewerken en draaien herbruikbare `TS-NNN-*.md` scenario's per app. +:::note Drie dingen die naar browsertesten klinken — houd ze uit elkaar +Er zijn drie losse "test"-oppervlakken en ze zijn makkelijk te verwarren: -De dispatcher van deze familie is **`test-counsel`**: hij coördineert alle acht personas tegen één feature en levert een gecombineerd rapport. Zelfde patroon als `hydra-gates` voor de quality-gates familie. +1. **`scripts/run-browser-tests.sh`** — een script dat via Playwright MCP inlogt op een draaiende app, de acceptatiecriteria van de spec afloopt en een verdict-JSON teruggeeft. Het is *runtime*-bewijs dat de feature werkt, maar alleen als jij het draait: geen enkele flow roept het aan, dus een pipeline-run levert geen browserbewijs op. +2. **gate-19 (`e2e-coverage`)** — een *statische* gate uit [deel 3](/nl/academy/hydra-tutorial-3-quality-gates). Hij draait helemaal geen browser; hij controleert dat elk spec-scenario via een `@e2e`-annotatie aan een Playwright-test *gekoppeld* is. Coverage-boekhouding, geen uitvoering. +3. **De `test-*`-familie** — testsuites die jij met de hand draait, vóór of na de pipeline. -:::note Geen overlap met de fabriek's browser-tester -De Hydra-fabriek heeft een **eigen** browser-tester (`hydra-ui-test`) die in de Browser UI Tester-stap draait. Die staat **los** van deze `test-*` familie. De `test-*` skills zijn voor wanneer jij — als mens — een feature wilt testen voordat of nadat de pipeline 'm heeft aangeraakt. Gates uit deel 3 draaien statische checks (regex/AST); `test-*` draait de echte applicatie in een browser. +Dus: gate-19 controleert dat de *koppelingen* bestaan en draait in de pipeline; het browser-script en de `test-*`-skills *besturen* echt een browser, en die start jij allebei zelf. Verschillende taken. ::: -### Familie 5: Utility & maintenance (~13 skills) — vrijwel allemaal ⌨️ voor mensen +### Familie 5: Utility & maintenance — allemaal ⌨️ voor mensen -De rest, dat is de overige ~13 skills. Vooral dev-comfort en meta-werk: +Al het overige. Vooral dev-comfort en meta-werk; een selectie: | Skill | Doet | |---|---| | `create-pr` | Maakt een PR vanuit een feature-branch — local checks → branch pick → PR-tekst. | -| `review-pr` | Reviewt een GitHub PR (NB: handmatige variant; de fabriek heeft zijn eigen Juan Claude). | -| `report-out` | End-of-day rapport: vandaag's commits + GitHub-activiteit → Nederlandse Slack-melding. | -| `clean-env` | Reset de lokale Docker-dev-omgeving volledig. | -| `local-run` | Lokale Nextcloud-dev-omgeving opstarten. | +| `review-pr` | Reviewt een PR (NB: handmatige variant; de fabriek heeft zijn eigen Juan Claude). | +| `report-out` | End-of-day-rapport: de commits van vandaag + GitHub-activiteit → Nederlandse Slack-melding. | +| `clean-env` | Reset de lokale OpenRegister-dev-omgeving (stoppen, volumes weg, herstarten, apps installeren). | +| `local-run` | Draait builder → reviewer → security lokaal op een wegwerp-repo; vanuit Claude Code doet hij wat `smoke-test.sh` doet (deel 5). | | `sync-docs` | Sync `{app}/docs/` of `.github/docs/claude/` met de werkelijkheid in de repo. | | `skill-creator` | Wizard voor het maken van een nieuwe skill (scaffold, frontmatter, evals). | -| `feature-counsel` | Pre-build spec-analyse vanuit acht persona-perspectieven (zusje van `test-counsel`). | -| `persistence-audit` | Audit hoe een app omgaat met data-persistence (objectstore, sessies, etc.). | +| `feature-counsel` | Pre-build spec-analyse vanuit negen persona-perspectieven (zusje van `test-counsel`). | +| `persistence-audit` | Audit een codebase of architectuur op persistence-vectoren na een compromittering: OAuth-grants, service accounts, CI/CD-secrets, IdP-trust, audit-trails. | | `journeydoc-init` / `journeydoc-add-story` / `journeydoc-instrument` | Handmatige instrumentatie + uitbreiding van Journey-docs. | | `verify-global-settings-version` | Check of `global-settings/VERSION` is opgehoogd na een wijziging. | -Niets uit deze familie draait in de pipeline. Het zijn jouw dagelijkse `/`-commando's. +Daarnaast staan er schrijf-skills (`writing`, `blog-write`, `tutorial-write`), de `parity-*`-skills voor capability-matrices tegenover concurrenten, en `atom-design` voor design-system-mocks. Niets uit deze familie draait in de pipeline. Het zijn jouw dagelijkse `/`-commando's. ## Vendor skills (community) Naast de eigen skills heeft Hydra **vendor skills** onder `hydra/vendor/skills/`: -- `code-review` — communautaire review-skill van Anthropic. → 🤖 Reviewer container. -- `trailofbits` — Semgrep-based static-analysis methodologie van Trail of Bits. → 🤖 Security container. -- `owasp` — OWASP-top-10:2025 + ASVS 5.0-checklists. → 🤖 Security container. +- `code-review` — communautaire review-skill van Anthropic. → 🤖 Reviewer-container. +- `trailofbits` — Semgrep-gebaseerde static-analysis-methodologie van Trail of Bits. → 🤖 Security-container. +- `owasp` — OWASP-top-10:2025 + ASVS 5.0-checklists. → 🤖 Security-container. -Die drie zitten dus **wél** in de fabriek (containers). Ze worden bij Juan en Clyde geladen voor extra dekking boven op de eigen `hydra-gate-*`. Onderhoud ligt bij externe partijen — updaten gaat door de bron te volgen, niet door zelf te editen. +Die drie zitten dus **wél** in de reviewer- en security-image; de builder-image heeft er geen van. Ze geven Juan en Clyde extra dekking boven op de eigen `hydra-gate-*`. Onderhoud ligt bij externe partijen — updaten gaat door de bron te volgen, niet door zelf te editen. ## Wanneer schrijf je een nieuwe skill? @@ -208,15 +217,15 @@ De pragmatische test: schrijf een skill als… 2. **Het is mechanisch beschrijfbaar** — je kunt het in 1-3 alinea's instrueren, zonder dat het ontaardt in "het hangt van de context af". 3. **Een persona of een mens zou er baat bij hebben.** Geen skill schrijven omdat het kan. -Voor een fout-positieve gate (deel 3): je past de bestaande skill aan, je schrijft geen nieuwe. Voor een nieuw klasse-fout dat je 3x ziet langskomen: ja, daar verdient het een eigen `hydra-gate-*` skill. +Voor een fout-positieve gate (deel 3): je past de bestaande skill aan, je schrijft geen nieuwe. Voor een nieuwe klasse fout die je 3x ziet langskomen: ja, die verdient een eigen `hydra-gate-*`-skill. - Hydra heeft een ingebouwde /skill-creator skill die je door het maken van een nieuwe skill leidt: scaffold, frontmatter, eval-harness, performance-meting. Roep hem aan vanuit een Claude-sessie in de hydra-repo om met de juiste structuur te starten in plaats van zelf een mapje op te bouwen. Een uitgewerkte walkthrough vind je in deel 2 van de Claude Skills-leerlijn; meten en evalueren wordt behandeld in deel 3. + Hydra heeft een ingebouwde /skill-creator-skill die je door het maken van een nieuwe skill leidt: scaffold, frontmatter, eval-harness, performance-meting. Roep hem aan vanuit een Claude-sessie in de hydra-repo om met de juiste structuur te starten in plaats van zelf een mapje op te bouwen. Een uitgewerkte walkthrough vind je in deel 2 van de Claude Skills-leerlijn; meten en evalueren wordt behandeld in deel 3. ## Test jezelf -Vier korte vragen om te checken of je dit deel begrepen hebt. Vastgelopen? Klik **Hint**. Curieus naar het antwoord? Klik **Antwoord**. +Vier korte vragen om te checken of je dit deel begrepen hebt. Vastgelopen? Klik **Hint**. Benieuwd naar het antwoord? Klik **Antwoord**.
@@ -237,9 +246,9 @@ In de frontmatter van een skill zet je: - `disable-model-invocation: true` — alleen handmatig. Gebruikt voor skills met side-effects (`/opsx-apply`, `/create-pr`). - `user-invocable: false` — alleen automatisch. Gebruikt voor achtergrondkennis die niet als actie zinvol is. -- Beide leeg → beide mogen. Default voor de meeste Hydra-skills. +- Beide leeg → beide mogen. De default voor de meeste Hydra-skills. -In de Hydra-pipeline gebruiken de containers vooral auto-activatie (Claude pikt zelf de juiste skill op); een mens op de CLI typt meestal expliciet `/`. +In de Hydra-pipeline typt niemand een `/`: elke stage krijgt een prompt die zegt wat hij moet doen en welke brief hij volgt, en elke skill die hij daarnaast laadt is Claude's eigen keuze. Een mens op de CLI typt meestal expliciet `/`. @@ -247,34 +256,38 @@ In de Hydra-pipeline gebruiken de containers vooral auto-activatie (Claude pikt
-**2. Welke skills draaien er nou écht in de pipeline-containers, en welke staan alleen in de repo voor mensen?** +**2. Wat gebruikt de automatische pipeline echt, welke skills dragen de container-images mee, en welke staan alleen in de repo voor mensen?**
-Drie containers (Builder, Reviewer, Security) hebben elk een specifieke set in hun image. Wat zit erin — en welke families staan er volledig buiten? +Houd de flow-gedreven run (prompt, brief, gate-suite) apart van de images die je zelf start. Wat draagt elke image mee — en welke families staan er volledig buiten?
-**In de pipeline-containers** (🤖 fabriek): +**In een flow-gedreven run** (🤖 fabriek) volgt een stage zijn prompt en brief: + +- **Build** — de prompt vraagt de change direct te implementeren; er wordt geen skill genoemd. Daarna draait de pipeline `scripts/run-hydra-gates.sh`, staat één fix-ronde toe, en draait de gates opnieuw. +- **Review-stages** — elke persona leest zijn brief uit `agents//` en `images/`, draait de gates, en moet `hydra-gates` in `checks_run` zetten. + +**In de container-images** (wat `dev-run.sh` en `smoke-test.sh` starten): -- **Builder** — alle `.claude/skills/*` ingebakken, maar de standaard-run is `opsx-apply`. De andere opsx-skills zijn er voor context. -- **Reviewer** — `hydra-gates` + 13 individuele `hydra-gate-*` + vendor `code-review`. -- **Security** — `hydra-gates` + 13 individuele `hydra-gate-*` + vendor `trailofbits` + vendor `owasp`. -- **Applier** — géén skills (puur CLAUDE.md). -- **Browser UI Tester** — alleen `hydra-ui-test`. +- **Builder** — alle skills uit `.claude/skills/`, geen vendor skills. Zijn entrypoint volgt `opsx-apply`. +- **Reviewer** — `hydra-gates` + 16 losse `hydra-gate-*` + vendor `code-review`. +- **Security** — `hydra-gates` + dezelfde 16 `hydra-gate-*` + vendor `trailofbits` (Semgrep) + vendor `owasp`. +- **Applier** — geen skills, alleen Read en Bash. -Plus: `scripts/run-hydra-gates.sh` draait alle **14** gates mechanisch in elke container, ongeacht welke skill-files in de image staan. +Wat de image ook meedraagt, `scripts/run-hydra-gates.sh` doet de detectie. Het declareert veel meer gates dan er gate-skills zijn; de COVERAGE-regel van de run zegt hoeveel. -**Niet in de fabriek**, alleen voor mensen op de CLI: +**Niet gebruikt door de fabriek** (sommige staan wel in de builder-image, maar geen stage roept ze aan), alleen voor mensen op de CLI: -- Vrijwel de hele `team-*` familie (7 skills). -- Vrijwel de hele `test-*` familie (19 skills) — de fabriek heeft zijn eigen `hydra-ui-test`. -- De meeste `opsx-*` skills behalve `opsx-apply`/`verify`/`archive` (humans starten changes; de pipeline implementeert ze). -- De hele utility-familie (`create-pr`, `review-pr`, `report-out`, `clean-env`, `sync-docs`, `skill-creator`, `feature-counsel`, `persistence-audit`, `journeydoc-*`, `verify-global-settings-version`). +- De hele `team-*`-familie. +- De hele `test-*`-familie — de pipeline draait helemaal geen browser. +- De `opsx-*`-skills (mensen bereiden changes voor; de pipeline bouwt ze). +- De hele utility-familie (`create-pr`, `review-pr`, `report-out`, `clean-env`, `local-run`, `sync-docs`, `skill-creator`, `feature-counsel`, `persistence-audit`, `journeydoc-*`, `verify-global-settings-version`, en de schrijf- en parity-skills). -In totaal: van ~70 skills in de repo gebruikt de automatische pipeline er een handvol; de rest is jouw gereedschap. +In totaal: van de zo'n 130 skills in de repo leunt de automatische pipeline op de gate-suite en één brief per persona; de rest is jouw gereedschap.
@@ -286,16 +299,16 @@ In totaal: van ~70 skills in de repo gebruikt de automatische pipeline er een ha
-Eén beslissing gaat over "de gate doet niet wat we al wilden". De ander over "we hebben een nieuwe categorie fout ontdekt". +Eén beslissing gaat over "de gate doet niet wat we al wilden". De andere over "we hebben een nieuwe categorie fout ontdekt".
-- **Bestaande aanpassen** bij een **fout-positief**: de gate triggert te breed of te smal op iets dat al bedoeld werd te checken. Voorbeeld: `hydra-gate-forbidden-patterns` matchte `$builder->add(` ten onrechte — je past de regex aan, je voegt geen tweede gate toe. -- **Nieuwe schrijven** voor een **nieuw klasse-fout** dat je 3× ziet langskomen en dat door alle bestaande mazen heen glipt. Voorbeeld: `hydra-gate-stub-scan` ontstond toen een builder een `return null;`-methode oplevert die PHPCS slikte en geen test had — dat was een nieuwe categorie, niet een fix op een bestaande check. +- **Bestaande aanpassen** bij een **fout-positief**: de gate triggert te breed of te smal op iets dat hij al hoorde te checken. Voorbeeld: `hydra-gate-forbidden-patterns` matchte `$builder->add(` ten onrechte — je past de regex aan, je voegt geen tweede gate toe. +- **Nieuwe schrijven** voor een **nieuwe klasse fout** die je 3× ziet langskomen en die door alle bestaande mazen glipt. Voorbeeld: `hydra-gate-stub-scan` ontstond toen een builder een `return null;`-methode opleverde die PHPCS slikte en geen test had — dat was een nieuwe categorie, geen fix op een bestaande check. -Rule of three: één keer is toeval, twee keer toeval, drie keer is een patroon dat een eigen gate verdient. +Rule of three: één keer is toeval, twee keer is toeval, drie keer is een patroon dat een eigen gate verdient.
@@ -307,13 +320,13 @@ Rule of three: één keer is toeval, twee keer toeval, drie keer is een patroon
-Denk aan herkomst (wie heeft ze geschreven?), bij welke pipeline-container ze worden ingeladen, en wat er gebeurt als je een externe partij hun werk moet updaten. +Denk aan herkomst (wie heeft ze geschreven?), in welke pipeline-image ze worden ingeladen, en wat er gebeurt als een externe partij hun werk moet updaten.
-Vendor-skills (`hydra/vendor/skills/`) zijn skills die **niet door ons** zijn geschreven, en zitten **wél** in de fabriek: +Vendor-skills (`hydra/vendor/skills/`) zijn skills die **niet door ons** zijn geschreven, en die **wél** in de reviewer- en security-image zitten: - `code-review` (Anthropic-community) → Reviewer-container (Juan Claude). - `trailofbits` (Trail of Bits, Semgrep-methodologie) → Security-container (Clyde). @@ -328,11 +341,11 @@ Apart houden omdat:
- + # uit `claude setup-token`; of ANTHROPIC Er is geen credentials-bestand. `scripts/lib/env-credentials.sh` is de enige resolver, en die faalt dicht: ontbreekt een variabele, dan stopt het script met een reden, in plaats van een run te starten die drie seconden later sneuvelt op een 401. De scripts geven beide waarden aan de containers door op variabelenaam (`-e GH_TOKEN`), zodat de waarden nooit in de proceslijst of in een bestand terechtkomen. - Een Claude Max- of Pro-abonnement is persoonlijk: het mag je eigen werk draaien, niet dat van een organisatie. Lokaal regelt dat zichzelf, omdat het script onder jouw shell met jouw token draait. In de pijplijn weigeren de flows een stage waarvan de eigenaar niet vast te stellen is. Deel 7 legt uit hoe. + Een Claude Max- of Pro-abonnement is persoonlijk: het mag je eigen werk draaien, niet dat van een organisatie. Lokaal regelt dat zichzelf, omdat het script onder jouw shell met jouw token draait. In de pijplijn komt de eigenaar van elke stage uit de stap die hem draait, en hydra-record-stage weigert een stage zonder eigenaar vast te leggen. Deel 7 vertelt meer. :::note Oudere setups @@ -80,9 +80,9 @@ HYDRA_IMAGE_TAG=test HYDRA_REVIEW_SCOPE=diff ``` -`HYDRA_FORGE=github` is goed voor elke ConductionNL-repo; `forgejo` is alleen voor een klant die een eigen Forgejo draait. Er is geen default, omdat een forge raden betekent dat je je token naar een derde partij stuurt. Begin je vanaf `secrets/.env.example`, controleer dan die regel: oudere versies zetten hem op `codeberg`. +`HYDRA_FORGE=github` is goed voor elke ConductionNL-repo; `forgejo` is alleen voor een klant die een eigen Forgejo draait. Er is geen default, omdat een forge raden betekent dat je je token naar een derde partij stuurt. `secrets/.env.example` zet hem op `github`; heb je een ouder voorbeeld gekopieerd, controleer dan die regel, want oudere versies zetten hem op `codeberg`. -Tip: de image-defaults zijn `ghcr.io/conductionnl/hydra` als prefix en `latest` als tag. Laat `HYDRA_IMAGE_*` weg om de gepubliceerde images te gebruiken. +Tip: de scripts die `HYDRA_IMAGE_*` lezen (`manual-review.sh`, `cron-audit.sh`, `cron-spec-from-issue.sh`) vallen terug op `localhost/hydra` en `test` als de variabelen leeg zijn, en `dev-run.sh` start altijd `localhost/hydra-:test`, wat je ook instelt. Lokaal bouw je de images dus zelf (stap 4), of je pullt een gepubliceerde `ghcr.io/conductionnl/hydra-`-image en tagt die onder de lokale naam. :::tip secrets/.env wordt automatisch geladen `scripts/dev-run.sh` weigert te starten zonder dit bestand en laadt het zelf, net als `reconcile.sh` en de `cron-*.sh`-sweeps. Een tijd lang controleerde `dev-run.sh` alleen óf het bestand bestond en laadde het nooit, waardoor `HYDRA_FORGE=github` ingesteld leek terwijl elke container nog met Codeberg praatte ([hydra#391](https://github.com/ConductionNL/hydra/issues/391)). Elk entrypoint dat configuratie leest MOET dit bestand bij opstart sourcen; dat is een repo-breed contract. @@ -90,7 +90,7 @@ Tip: de image-defaults zijn `ghcr.io/conductionnl/hydra` als prefix en `latest` ## Stap 4: build de container images -In CI pusht GitHub Actions alle images naar GHCR. Lokaal, met `HYDRA_IMAGE_PREFIX=localhost/hydra`, bouw je ze zelf: +In CI bouwt de workflow `hydra-image.yml` de base-, builder-, reviewer- en security-image en pusht ze naar GHCR als `ghcr.io/conductionnl/hydra-*`. Lokaal bouw je ze zelf, onder de namen waar de scripts naar zoeken: ```bash # Base image: Nextcloud + PostgreSQL + OpenRegister (builder-dependency) @@ -106,7 +106,7 @@ docker build -t localhost/hydra-security:test -f images/security/Dockerfile . docker network create hydra-net ``` -Controleer daarna met `docker images | grep hydra` dat de drie tags er staan. Reviewer en security zijn standalone (geen NC-dependency); de builder bouwt op de `hydra-nextcloud-test`-base. +Controleer daarna met `docker images | grep hydra` dat de drie tags er staan. Reviewer en security zijn standalone (geen NC-dependency). De builder kopieert de Nextcloud-boom en de database uit `ghcr.io/conductionnl/hydra-nextcloud-test:stable32` met `COPY --from`, dus die image moet onder precies die naam bestaan voordat je de builder bouwt. ## Stap 5: draai één stage met `dev-run.sh` @@ -143,21 +143,24 @@ Het script hernoemt een oudere `todo-app` naar `todo-app-runN`, maakt een verse ## Stap 7: een echte run op een doel-app -Op een echte app start je zelf geen containers. Je zet het trigger-label op het tracking-issue en de flows doen de rest: +Op een echte app start je zelf geen containers. Je zet het trigger-label **`build:queued`** op het issue en de flows doen de rest. Eerst moeten twee dingen kloppen: -- Het trigger-label `ready-to-build`. -- Optioneel `yolo`, als de PR mag auto-mergen zodra alles groen is. -- Optioneel `openspec` als je via OpenSpec werkt. +- De **body** van het issue noemt de change als pad, `openspec/changes/`. De sequencer neemt het eerste zulke pad dat hij vindt. Geen pad betekent `build:blocked`. +- Die change-map bestaat op de base-branch: `development` als de repo die heeft, anders de default branch. Zo niet, dan wordt het ook `build:blocked`. -Via de Hydra-helper: +Zet daarna het label via de Hydra-helper: ```bash -./scripts/hydra-label.sh ConductionNL/ add ready-to-build +./scripts/hydra-label.sh ConductionNL/ add build:queued ``` `scripts/hydra-label.sh` is het **preferred path**: het gebruikt de label-helpers uit `scripts/lib/labels.sh` en synct het GitHub Project board realtime. Een directe `gh issue edit --add-label` werkt ook, maar dan blijft het board in de oude kolom staan tot reconcile (elke 10 minuten) het oppikt. -De `hydra-dispatch`-flow vindt het label bij zijn volgende tik van vijf minuten en geeft het issue door aan `hydra-sequencer`. De definitie wordt geleverd met `enabled: false`, dus dit gebeurt alleen op een OpenRegister-instantie waar de flow aangezet is. Deel 7 behandelt die opzet. +De flow `hydra-sequencer` vindt het label bij een van zijn tikken van vijf minuten. Elke tik zoekt hij in de org naar open issues met een queued-label en doet hij één stuk werk; review-labels gaan vóór `build:queued`, dus bij een volle wachtrij kan het een paar tikken duren voor jouw issue aan de beurt is. De definitie wordt geleverd met `enabled: false`, dus dit gebeurt alleen op een OpenRegister-instantie waar de flow aangezet is. Deel 7 behandelt die opzet. + +:::note ready-to-build en yolo +Oudere versies van dit deel gebruikten `ready-to-build` als trigger en `yolo` voor een auto-merge. `create-labels.sh` maakt beide labels nog aan, maar geen enkele flow leest ze. De trigger is `build:queued`, en er wordt niets automatisch gemerged. +::: Eerdere versies van dit deel besteedden een hele stap aan HYDRA_LABEL_PREFIX. Die gaf de lokale supervisor van elke developer een eigen label-namespace (wilco-build:queued), zodat twee supervisors op dezelfde repo elkaars werk niet oppakten. De supervisor is weg, en de flows zijn het enige dat nog werk oppakt. Die reageren alleen op de kale labels. De shell-tooling (create-labels.sh --prefix, hydra-label.sh, reconcile.sh) kent de variabele nog, maar geen enkele flow pakt een geprefixt label op. Laat hem leeg. @@ -165,23 +168,32 @@ De `hydra-dispatch`-flow vindt het label bij zijn volgende tik van vijf minuten ## Stap 8: volg de pijplijn -Zodra de sequencer het issue heeft, gebeurt dit: +Zodra de sequencer het issue heeft, bewegen de labels zo: ``` -ready-to-build → build:queued → build:running → build:pass - ↳ code-review:queued → :running → :pass / :fail - ↳ security-review:queued → :running → :pass / :fail - ↳ applier:queued → :running → :pass / :fail → done / needs-input +build:queued → build:pass | build:fail | build:no-change | build:blocked + (bij pass en bij fail wordt een PR geopend; die wordt nooit gemerged) + +build:pass → jij zet zelf code-review:queued + +code-review:queued → code-review:pass (+ security-review:queued) | code-review:fail | needs-input +security-review:queued → security-review:pass (+ applier:queued) | security-review:fail | needs-input +applier:queued → applier:pass | applier:fail | needs-input ``` -Je volgt het op twee plekken: +Een paar dingen die je makkelijk verwacht en die niet gebeuren: + +- **Niets zet code review in de wachtrij na `build:pass`.** Doe dat zelf: `./scripts/hydra-label.sh ConductionNL/ add code-review:queued`. +- **Er zijn geen `:running`-labels.** Terwijl een stage draait, blijft zijn `:queued`-label op het issue staan en markeert een lock het issue als onderhanden. +- **Een `fail` stopt de keten.** Een afgekeurde code review gaat niet door naar security review. +- **`applier:pass` is het eind.** Er is geen `done`-label en geen merge; een mens merget de PR. -1. **GitHub issue**: de labels veranderen terwijl de sequencer de stages doorloopt. -2. **GitHub PR**: de commits van de builder en de inline comments van de reviewers. +Je volgt het op twee plekken: -Achter beide wordt elke stage vastgelegd in `openspec/changes//hydra.json`, voordat het label verschuift. Deel 6 laat zien hoe je dat bestand leest. De flow-runs zelf leven in OpenRegister; er is geen logbestand op je machine om te tailen. +1. **GitHub issue**: de labels veranderen terwijl de sequencer de stages doorloopt. De sequencer schrijft die labels zelf, dus de kaart op het project-board volgt niet meteen; `reconcile.sh` verplaatst hem bij zijn volgende sweep. +2. **GitHub PR**: de build-commit op `feature//hydra-build`, met de gate-output in de PR-body. Elke review-stage schrijft zijn verdict naar een bestand `hydra-verdict.json`, dat de sequencer bij de volgende tik ophaalt; dat verdict verschuift de labels. -`yolo` gezet en alles groen? Dan zie je op het issue `done` verschijnen, op de PR een approve en een auto-merge. Geen `yolo`? Dan staat de PR klaar voor een mens om te mergen. +Achter beide legt de sequencer, nadat de labels van een stage verschoven zijn, de stage vast in `openspec/changes//hydra.json` via de flow `hydra-record-stage`, die een stage zonder eigenaar weigert. Deel 6 laat zien hoe je dat bestand leest. De flow-runs zelf leven in OpenRegister; er is geen logbestand op je machine om te tailen. ## Troubleshooting @@ -195,17 +207,20 @@ Achter beide wordt elke stage vastgelegd in `openspec/changes//hydra.json` Zet HYDRA_FORGE=github in secrets/.env. De weigering is bewust: zonder die regel zou Hydra moeten raden waar je token heen moet. - - Controleer dat secrets/.env daadwerkelijk HYDRA_IMAGE_PREFIX=localhost/hydra en HYDRA_IMAGE_TAG=test bevat, en dat de tags uit stap 4 overeenkomen. + + dev-run.sh gebruikt altijd localhost/hydra-<stage>:test en negeert HYDRA_IMAGE_*, dus bouw met precies die tags. Bij manual-review.sh en de cron-scripts: controleer waar HYDRA_IMAGE_PREFIX en HYDRA_IMAGE_TAG in secrets/.env op uitkomen; leeg betekenen ze localhost/hydra en test. - docker images | grep hydra-builder. Geen treffer? Bouw eerst de base image (hydra-nextcloud-test) en daarna pas de builder. De builder-Dockerfile heeft de base als FROM. Een fout network hydra-net not found betekent dat je de laatste regel van stap 4 hebt overgeslagen. + docker images | grep hydra-builder. Geen treffer? Bouw eerst de base image (hydra-nextcloud-test) en daarna pas de builder. De builder-Dockerfile kopieert uit de base met COPY --from, dus de base moet bestaan onder de naam ghcr.io/conductionnl/hydra-nextcloud-test:stable32. Een fout network hydra-net not found betekent dat je de laatste regel van stap 4 hebt overgeslagen. - Niets op je machine let op labels. Op een echte app moet de hydra-dispatch-flow aangezet zijn in de OpenRegister-instantie (deel 7). Wil je lokaal een stage proberen, gebruik dan dev-run.sh uit stap 5. + Niets op je machine let op labels. Op een echte app moet de flow hydra-sequencer aangezet zijn in de OpenRegister-instantie (deel 7). Controleer ook de exacte naam van het label: met HYDRA_LABEL_PREFIX gezet schrijft hydra-label.sh <prefix>-build:queued, en dat leest geen enkele flow. En de sequencer doet één stuk werk per tik, review-stages eerst, dus een volle wachtrij vertraagt je build. Wil je lokaal een stage proberen, gebruik dan dev-run.sh uit stap 5. + + + De sequencer kon de change niet vinden. De body van het issue moet openspec/changes/<naam> bevatten, en die map moet bestaan op development (of op de default branch, als de repo geen development heeft). Pas de body aan of push de change, haal dan build:blocked weg en zet build:queued opnieuw. - - Stale labels van een vorige run blijven hangen. Strip de oude stage-labels met ./scripts/hydra-label.sh ConductionNL/<app> <N> transition ready-to-build. Dat zet het issue terug in een gedefinieerde starttoestand. + + Er staan nog review-labels van een vorige run op het issue, en die pakt de sequencer vóór build:queued. Zet het issue terug met ./scripts/hydra-label.sh ConductionNL/<app> <N> transition build:queued: dat haalt alle andere queued-labels, needs-input en het oude build-verdict in één keer weg, en laat alleen build:queued staan als starttoestand. @@ -291,7 +306,7 @@ Wat laat een run achter als er geen flow bij betrokken is? - De drie typische oorzaken van een vastgelopen pijplijn: stale dependencies, terechte review-findings, en fundamenteel verkeerde build-aanpak. - De interventie-ladder: development-merge → review-labels resetten → retry:queued → rebuild:queued → handmatige fix. - Waarom needs-input NIET hetzelfde is als "Hydra is kapot". - Waar je de retrospectives vindt en hoe je terugkerende patronen herkent. + Wat elke uitkomst betekent: de vier build-resultaten, en needs-input, dat alleen een review-stage zet. + De interventie-ladder: verouderde PR bijwerken → review-stage (opnieuw) in de wachtrij → een needs-input-stage vrijmaken → de build opnieuw in de wachtrij → handmatige fix. + Waarom retry:queued en rebuild:queued vandaag niets doen. + Waar je kijkt als iets vastzit, en hoe je terugkerende patronen herkent. Deel 1 t/m 5 doorgenomen. - Een mentaal model van het label-state-machine (uit deel 2). + Een beeld van de label-volgorde uit stap 8 van deel 5. -## Wat betekent `needs-input`? +## Wat betekenen de uitkomsten? -`needs-input` is **geen failure-state in de zin van "Hydra heeft gefaald"**. Het is een expliciete escalatie: Hydra heeft gedaan wat hij kon, één run, en is tot een terminaal punt gekomen waar hij niet zelf verder kan zonder een menselijke beslissing. +De twee banen van `hydra-sequencer` eindigen verschillend, en het helpt om ze uit elkaar te houden. -Mogelijke oorzaken: +**De build-baan** eindigt in een van vier labels. Geen daarvan is `needs-input`: -- Beide reviewers `pass` maar de quality-recheck na hun fixes is rood (een fix introduceerde een nieuwe overtreding). -- Eén van beide reviews `:fail`. -- Applier Axel Pliér `:fail`. -- Build crashed mid-stage (container OOM, rate-limit op alle accounts, …). -- Agent-maxed-out: een persona heeft zijn turn-budget opgemaakt. +| Label | Wat er gebeurde | Is er een PR? | +|---|---|---| +| `build:pass` | De gates waren groen, meteen of na de ene fix-ronde. | Ja | +| `build:fail` | De gates waren na de ene fix-ronde nog rood. | Ja, met de gate-output in de body | +| `build:no-change` | De agent veranderde niets, dus er viel niets te pushen. | Nee | +| `build:blocked` | De body van het issue noemt geen `openspec/changes/`, die change bestaat niet op de base-branch, of de build kon niet draaien. | Nee | -Je rol als mens: bepaal welke van de vier opties hieronder het meest passend is en pas die toe. **Niet** een nieuwe `retry:queued` blind plakken in de hoop dat het deze keer wel werkt. +**De review-baan** (code review → security review → applier) sluit elke stage af met `:pass`, `:fail` of `needs-input`. `needs-input` is **geen oordeel over de code**. Het betekent dat de stage geen bruikbaar verdict opleverde: + +- het verdict-bestand `hydra-verdict.json` ontbreekt of is onleesbaar; +- het verdict noemt geen `hydra-gates` in `checks_run`, dus er is geen bewijs dat de gates gedraaid hebben; +- de stage kon helemaal niet draaien. + +Een `:fail` is wél een echt verdict: de reviewer heeft gekeken en zei nee. Na een `:fail` wordt niets meer in de wachtrij gezet, en na `build:pass` ook niet: `code-review:queued` zet je zelf. + +Jouw rol als mens: uitzoeken welke van deze je voor je hebt, en de goedkoopste hendel hieronder kiezen die past. ## De interventie-ladder -| # | Situatie | Interventie | Kosten | Wat behoud je | +Elke hendel is een label. Zet het met `scripts/hydra-label.sh`, zodat het project-board meebeweegt. + +| # | Situatie | Interventie | Kosten | Wat blijft behouden | |---|---|---|---|---| -| 1 | Development is doorgegroeid, PR staat stale (nieuwe lint-rules, ADRs, fixtures op development) | gh pr update-branch <N> (of de cron pakt het op) | Gratis | Alle build/review/applier-werk. Alleen dependencies vernieuwen. | -| 2 | Review-contracten zijn aangepast en je wilt reviewers opnieuw laten oordelen zonder rebuild | Reviewer-labels resetten: stage-labels strippen, code-review:queued opnieuw zetten. PR blijft. | 2× reviewer + applier | De builder-output. Alleen het review-deel rewindt. | -| 3 | Reviewers/applier flagden concrete findings en de builder hoeft alleen even bij te schaven | retry:queued op het issue plakken | 1× builder + 2× reviewer + applier | Branch + PR blijven. Orchestrator bouwt feedback.md uit hydra.json (unfixed + applier-blockers + reeds gefixed) en dispatcht builder in HYDRA_MODE=fix. Single-shot — geen loop. | -| 4 | Builder-output was fundamenteel verkeerd (verkeerde aanpak, stub-implementatie, fundamentele requirements gemist) | rebuild:queued op het issue plakken | Volledige cyclus | Orchestrator sluit de open PR, hard-reset feature/<issue>/* naar development, strip alle cycle-labels, zet build:queued. Volgende cyclus begint van scratch. | -| 5 | Je hebt zelf de PR al gesloten en stale labels staan nog op het issue | Wachten op reconcile.sh (om de 10 min) | Volgende reconcile-run | check_stage_without_open_pr ziet de inconsistentie en zet automatisch build:queued. Self-healing. | -| 6 | Pipeline-infra is stuk (container crash, alle tokens op, image niet vindbaar) | Escaleer naar mens (jij of een teamlead), los infra op, en kies pas DAN één van 1-4 hierboven | Tijd | — | +| 1 | Development is verder gegaan en de PR is verouderd (nieuwe lint-regels, ADR's, fixtures op development) | gh pr update-branch <N>, of wacht op cron-update-prs.sh (elk uur) | Gratis | Alles. Alleen de base wordt bijgewerkt. | +| 2 | build:pass en nog niemand heeft gereviewd | Zet code-review:queued | Tot drie review-stages | De build. | +| 3 | Review-contracten zijn aangepast, of een review moet opnieuw | Haal de :pass/:fail van die stage weg (en needs-input), zet <stage>:queued | Eén tot drie review-stages | De build. Bij een pass zet de sequencer de volgende stage weer in de wachtrij. | +| 4 | Een review-stage eindigde in needs-input | Haal **eerst** het :queued-label van die stage weg, los de oorzaak op, haal dan needs-input weg en zet :queued opnieuw (zie hieronder) | Eén stage | De build en de eerdere verdicts. | +| 5 | De build ging mis: build:fail, build:no-change, build:blocked, of een build:pass die toch niet klopt | Haal het build:*-verdict weg, zet build:queued | Een volledige build | Niets van de oude build. De sequencer bouwt opnieuw vanaf de base-branch en pusht feature/<issue>/hydra-build. | +| 6 | Je hebt de PR zelf gesloten en er staan nog oude labels op het issue | Wacht op reconcile.sh (elke 10 min) | Volgende reconcile-run | check_stage_without_open_pr ziet de inconsistentie en zet build:queued. Herstelt zichzelf. | +| 7 | De pijplijn-infra is kapot (flow uitgezet, een workload-stap die niet kan starten, een credential die de broker niet kan resolven) | Eerst de infra repareren, en pas DAN een van 1–5 kiezen | Tijd | — | + +**Volgorde:** altijd de goedkoopste die past. Grijp alleen naar een nieuwe build als de build zelf fout was; anders gooi je goed werk weg. + +### Een review-stage opnieuw in de wachtrij zetten + +Stel: de code review is afgekeurd en je vindt dat hij opnieuw moet, omdat een ADR veranderd is of omdat je zelf een fix op de branch hebt gepusht: + +```bash +./scripts/hydra-label.sh ConductionNL/ remove code-review:fail +./scripts/hydra-label.sh ConductionNL/ add code-review:queued +``` + +De sequencer pakt hem op bij een van de volgende tikken. Keurt de review goed, dan zet hij zelf `security-review:queued`, en loopt de keten vanaf daar verder. + +### Een review-stage die in `needs-input` eindigde + +:::warning Haal eerst het `:queued`-label weg +Een stage die in `needs-input` eindigt, **houdt zijn `:queued`-label**. De sequencer zoekt alleen op dat label, dus hij pakt de stage bij de volgende tik weer op — elke vijf minuten — tot iemand het weghaalt. Haal het label weg voordat je gaat uitzoeken wat er mis is. +::: + +```bash +# 1. Stop de herhalingen +./scripts/hydra-label.sh ConductionNL/ remove security-review:queued + +# 2. Los de oorzaak op: gates niet gedraaid, geen verdict-bestand, of een stage die niet kon starten + +# 3. Zet hem opnieuw in de wachtrij +./scripts/hydra-label.sh ConductionNL/ remove needs-input +./scripts/hydra-label.sh ConductionNL/ add security-review:queued +``` + +Geef altijd de **kale** labelnaam mee. Met `HYDRA_LABEL_PREFIX` gezet voegt `hydra-label.sh` de prefix zelf toe, dus `wilco-needs-input` zou `wilco-wilco-needs-input` worden. En omdat de flows alleen kale labels lezen, laat je de prefix sowieso leeg (deel 5). + +### De build opnieuw in de wachtrij zetten + +```bash +./scripts/hydra-label.sh ConductionNL/ remove build:fail +./scripts/hydra-label.sh ConductionNL/ add build:queued +``` -**Volgorde:** altijd 1 → 2 → 3 → 4. Pak het goedkoopste herstel eerst. Reach voor `rebuild:queued` alleen als de build zelf fundamenteel verkeerd was. +Vervang `build:fail` door het build-verdict dat op het issue staat. Staan er ook nog oude review-labels op, dan doet `./scripts/hydra-label.sh ConductionNL/ transition build:queued` het in één keer: het haalt alle andere queued-labels, `needs-input` en het oude build-verdict weg, en laat `build:queued` staan. -### `retry:queued` in detail +Wat de push doet met een bestaande branch `feature//hydra-build`, en of er een nieuwe PR naast een oude wordt geopend, wordt buiten de flow-definities bepaald. Wil je een schone PR, sluit de oude dan eerst. -1. Jij (of een automated rule) plakt `retry:queued` op het issue. Met label-prefix wordt dat `-retry:queued` — `scripts/hydra-label.sh` regelt dat automatisch. -2. Supervisor pakt het op als reguliere queue-job. -3. Orchestrator vindt de open PR op `feature//*`. -4. Orchestrator cloned development, kopieert `openspec/changes//hydra.json` en draait `scripts/lib/build-feedback-brief.py` → schrijft `feedback.md` met (a) applier-blockers, (b) unfixed reviewer-findings, (c) reeds-gefixed items, (d) Scope-lijst van alle geflagde files. -5. Orchestrator flipped `retry:queued` → `retry:running`, dispatcht de builder met `HYDRA_MODE=fix` en `feedback.md` gemount op `/workspace/feedback.md`. -6. Builder leest de brief, fixed álle bevindingen in één pass, beperkt zich tot Scope-files (plus nieuwe files die expliciet door een blocker geëist worden), commit met `fix (retry):` messages, pusht. -7. Succes: orchestrator strip alle review/applier-labels + `retry:running` + `needs-input`, zet `code-review:queued`. Reviewers draaien opnieuw tegen de gefixede code. -8. Faal: `retry:running` → `needs-input`, comment op het issue legt uit waarom en wijst naar `rebuild:queued` als de volgende hefboom. +### En `retry:queued` en `rebuild:queued`? -**Geen loop.** Eén `retry:queued` = één iteratie. Als de fix-builder pusht en de volgende review weer faalt, kun je opnieuw `retry:queued` aanvragen — maar dat is dan een **nieuwe** single-shot, niet een auto-retry. +**Die doen vandaag niets.** Geen enkele flow leest die labels. Eerdere versies van dit deel beschreven wat ze deden onder de uitgefaseerde shell-orchestrator: `retry:queued` stelde uit `hydra.json` een `feedback.md` samen en draaide de builder nog één keer in `HYDRA_MODE=fix`, en `rebuild:queued` sloot de PR en zette de branch hard terug. Die orchestrator is weg, en dat gedrag ook; `scripts/lib/build-feedback-brief.py` heeft geen aanroeper meer. + +`create-labels.sh` maakt beide labels nog aan, en `cron-update-prs.sh` zet `rebuild:queued` nog steeds op het issue van een PR met merge-conflicten, samen met een comment. Niets pakt dat label op, dus zie de comment als het signaal: los het conflict op, of zet de build opnieuw in de wachtrij (trede 5). ## Wanneer is het de gate, en wanneer is het de builder? -Een terugkerende valkuil uit eigen retrospectives: het issue gaat 3 keer naar `needs-input` om dezelfde reden — typisch `spdx-headers: fail` of een gelijksoortige mechanische gate. Iedere `retry:queued` reproduceert hetzelfde patroon. +Een terugkerende valkuil uit onze eigen retrospectives: hetzelfde issue faalt drie keer op dezelfde mechanische gate — typisch een ontbrekende SPDX-header of een vergelijkbare hygiëne-check. Elke opnieuw gestarte build reproduceert hetzelfde patroon. -In dat geval is een vierde retry **niet** de juiste move. Wees discplineerd: +Een vierde build is dan **niet** de juiste zet. Wees gedisciplineerd: -1. **Sanity-check de gate zelf.** Is dit een fout-positief? (Zie deel 3.) Een klassieker: een grep zonder word-boundary die meer matcht dan bedoeld. Fix in dat geval `scripts/run-quality.sh` of de gate-skill. -2. **Hand-fix de mechanische overtreding.** Soms is het sneller om zelf de SPDX-headers toe te voegen, te committen op de feature branch en de reviewers opnieuw aan te roepen via label-reset (situatie 2 hierboven). Tijd: minuten. -3. **Overweeg een sterkere agent.** Als hetzelfde klasse-fout op meerdere repos dezelfde "blind spot" vertoont, is dat een signaal dat de huidige builder-prompt of de mechanische gate niet voldoende is. Open een issue in `hydra/` om de gate aan te scherpen of een "housekeeping"-agent te bouwen. +1. **Check de gate zelf.** Is dit een fout-positief? (Zie deel 3.) Een klassieker: een grep zonder woordgrens die meer matcht dan bedoeld. De gates zitten in het package `conduction/hydra-gates` (in `ConductionNL/.github`); repareer de gate daar, of zijn skill in `hydra/`. +2. **Fix de mechanische overtreding met de hand.** Vaak is het sneller om de SPDX-headers zelf toe te voegen, te committen op `feature//hydra-build`, en de code review in de wachtrij te zetten (trede 2 of 3). Tijd: minuten. +3. **Overweeg een sterkere agent.** Toont dezelfde klasse fout dezelfde blinde vlek in meerdere repo's, dan is dat een signaal dat de build-prompt of de mechanische gate niet volstaat. Open een issue in `hydra/` om de gate of de brief aan te scherpen. ## Watch-list patronen -Patronen die we al hebben gezien en die het waard zijn om eerst tegen je situatie te leggen: +Patronen die we al gezien hebben en die het waard zijn om eerst met jouw situatie te vergelijken. De eerste drie komen uit de shell-pijplijn die de flows vervingen; de lessen gelden nog steeds. - Op decidesk (nu Decidiq) GitHub-issues #44/#71/#72, hetzelfde patroon: builder mist EUPL-1.2 header op nieuwe PHP-files, reviewer claimt 0 fixes nodig, recheck slaat rood. Forbidden-patterns bleek deels fout-positief (grep zonder word-boundary). Fix: handmatig SPDX toegevoegd + grep aangescherpt. + Op decidesk (nu Decidiq) GitHub-issues #44/#71/#72 steeds hetzelfde patroon: de builder vergat de EUPL-1.2-header op nieuwe PHP-files, de reviewer claimde dat er 0 fixes nodig waren, de recheck werd rood. Forbidden-patterns bleek deels fout-positief (grep zonder woordgrens). Fix: SPDX met de hand toegevoegd + de grep aangescherpt. - Vijf onderscheiden pipeline-bugs in één retrospectief. Builder leverde op decidesk (nu Decidiq) GitHub-issues #44/#45 een methode op met return null; + TODO; PHPCS slikte het; geen test; productie-bug. Resulteerde in hydra-gate-stub-scan + ADR-021 (bounded-fix scope op shape, niet op lijntelling). Volledige lessen in decidesk-44-45-phase-g.md. + Vijf verschillende pijplijn-bugs in één retrospectief. De builder leverde op decidesk (nu Decidiq) GitHub-issues #44/#45 een methode met return null; + TODO; PHPCS slikte het; geen test; productiebug. Resulteerde in hydra-gate-stub-scan + ADR-021 (bounded-fix-scope op vorm, niet op aantal regels). Alle lessen in decidesk-44-45-phase-g.md. - - Completion-handler in een tight loop poste elke tick een nieuwe escalation-comment op een al-terminaal issue. Fix: terminal-state guard vóór alle side-effects (zie CLAUDE.md sectie "Terminal-state guards"). Als jij een issue ziet met >3 identieke needs-input comments: open een infra-bug, niet een retry. + + Een completion-handler in een strakke lus plaatste elke tik een nieuwe escalatie-comment op een issue dat al terminaal was. Fix: een terminal-state guard vóór alle side-effects (zie CLAUDE.md, sectie "Terminal-state guards"). De sequencer heeft zo'n guard vandaag niet: een geblokkeerde review-stage krijgt geen nieuwe comment, maar wordt elke tik opnieuw gedraaid, tot zijn :queued-label weg is. Zie je dezelfde stage steeds weer draaien, haal dat label dan weg en open een infra-issue; zet niets opnieuw in de wachtrij. ## Diagnostics: waar kijk je? -Snelle checklist als een issue naar `needs-input` gaat: +Er is geen logbestand op je machine om te tailen. Snelle checklist als een issue vastzit: ```bash # 1. Welke labels staan er nu? gh issue view --repo ConductionNL/ --json labels --jq '[.labels[].name]' -# 2. Wat zegt hydra.json (mits aanwezig op de feature branch)? -gh api repos/ConductionNL//contents/openspec/changes//hydra.json?ref=feature// \ - --jq '.content' | base64 -d | jq '.cycles[-1] | {outcome, outcome_reason, pattern_tags}' +# 2. Is er een PR, en wat staat er in de body (gate-output)? +gh pr list --repo ConductionNL/ --head feature//hydra-build --state all -# 3. De supervisor-log van de relevante tijdspanne -grep "issue/" logs/supervisor.log | tail -50 +# 3. Wat zegt hydra.json op de feature-branch over de laatst vastgelegde stage? +gh api "repos/ConductionNL//contents/openspec/changes//hydra.json?ref=feature//hydra-build" \ + --jq '.content' | base64 -d | jq '.cycles[-1]' +``` -# 4. Pipeline-logs (per stage) op de feature branch -gh api repos/ConductionNL//contents/openspec/changes//pipeline-logs?ref=feature// +De vierde plek zijn de **flow-runs** van `hydra-sequencer` in de OpenRegister-instantie: elke tik is daar één run. Kijk daar als de labels en `hydra.json` niet verklaren wat er gebeurde — bijvoorbeeld als een stage niet kon starten, of zijn verdict niet gelezen kon worden. + +### hydra.json lezen (schema v2) + +Nadat de labels van een stage verschoven zijn, roept de sequencer de flow `hydra-record-stage` aan, die een entry toevoegt aan `openspec/changes//hydra.json` op `feature//hydra-build` en die via de API commit. Een stage zonder eigenaar weigert hij: het record moet zeggen wie hem draaide, op wiens credential. Elke vastgelegde stage wordt een nieuwe entry in `cycles[]`: + +```jsonc +{ + "schema_version": 2, + "app": "decidiq", + "repo": "ConductionNL/decidiq", + "issue": 15, + "spec_slug": "p2-meeting-management", + "cycles": [ + { + "cycle": 3, + "outcome": "failed", // ← de kop: passed | failed + "outcome_reason": "build: 2 gate(s) failed", + "started_at": "", + "stages": [ + { + "stage": "build", + "verdict": "fail", + "failures": 2, // aantal gefaalde gates + "exit_code": 2, + "output_tail": "…de gate-output…", // wat de gates echt zeiden + "owner": "", + "credential_owner": "", + "credential_name": "" + } + ] + } + ] +} ``` -`hydra.json` is je belangrijkste bron. De `outcome_reason` en `pattern_tags` van de laatste cycle leggen meestal in één regel uit wat er misging. +Lees het van boven naar beneden: `outcome` en `outcome_reason` van de **laatste** entry in `cycles[]` vertellen je *wat* er gebeurde, en `output_tail` vertelt je *welke* gates dat zeiden. Twee kanttekeningen. De record-stap noemt op dit moment elke stage `build`, review-stages inbegrepen, dus lees het veld `stage` niet als bewijs van welke stage draaide. En een mislukte record-write stopt de run niet, dus een ontbrekende entry betekent niet dat de stage niet draaide; kijk naar de labels en de flow-runs. + + + Records die de shell-pijplijn schreef vóór de overstap naar flows bevatten meer: findings[] met een status per finding, pattern_tags zoals turn-budget-exhausted of reviewer-skipped-full-suite, depends_on, aantallen turns. De flows schrijven die velden niet. Open je een ouder hydra.json, dan is dat waarom het er anders uitziet. + ## Wanneer is "menselijk overnemen" gewoon goed? -Niet elk `needs-input`-issue verdient een geautomatiseerd fix-pad. Soms is de meest pragmatische actie: **mens neemt over, los het op in een eigen commit op de feature branch, push, mark resolved**. +Niet elk vastgelopen issue verdient een geautomatiseerd pad. Soms is de meest pragmatische actie: **een mens neemt over, fixt het in een commit op de feature-branch, pusht, en zet de review in de wachtrij**. Voorbeelden: -- Een kleine semantische beslissing die de Builder niet kan maken zonder context buiten de change (bijv. "moet dit veld optional of required zijn?"). -- Een interactie met een externe partij (open ticket bij een leverancier). -- Een gate die fout-positief is en een fix in `hydra/` zelf vereist (waarover je niet binnen één retry-cyclus uit bent). +- Een kleine semantische beslissing die de builder niet kan nemen zonder context buiten de change (bijv. "moet dit veld optioneel of verplicht zijn?"). +- Een interactie met een externe partij (openstaand ticket bij een leverancier). +- Een gate die fout-positief is en een fix in het gate-package zelf vraagt (die los je niet op binnen één build). -Hydra is een fabriek, geen ideologie. Pak de menselijke shortcut als die billijker is. +Hydra is een fabriek, geen ideologie. Neem de menselijke shortcut als dat de eerlijkere keuze is. ## Test jezelf -Vier korte vragen om te checken of je dit deel begrepen hebt. Vastgelopen? Klik **Hint**. Curieus naar het antwoord? Klik **Antwoord**. +Vier korte vragen om te checken of je dit deel begrepen hebt. Vastgelopen? Klik **Hint**. Benieuwd naar het antwoord? Klik **Antwoord**.
@@ -140,21 +223,22 @@ Vier korte vragen om te checken of je dit deel begrepen hebt. Vastgelopen? Klik
-Eén dimensie: kosten. De andere: hoeveel reeds-gedaan werk je verliest. +Eén dimensie: kosten. De andere: hoeveel al gedaan werk je kwijtraakt.
-Van **goedkoop naar duur**, met als doel zo veel mogelijk reeds-gedaan werk te behouden: +Van **goedkoop naar duur**, met als doel zo veel mogelijk al gedaan werk te behouden: -1. **`gh pr update-branch`** — gratis. Alle build/review/applier-werk blijft staan, alleen dependencies vernieuwen. -2. **Reviewer-labels resetten** — kost 2× reviewer + applier. Builder-output blijft. -3. **`retry:queued`** — kost 1× builder + 2× reviewer + applier. Branch + PR blijven; builder fixed gescoped via `feedback.md`. -4. **`rebuild:queued`** — volledige cyclus. PR sluit, branch reset, vanaf scratch. -5. **Handmatige fix** — als de loop op een fout-positieve gate hangt of een mens een semantische beslissing moet maken. +1. **`gh pr update-branch`** — gratis. Alles blijft; alleen de base wordt bijgewerkt. +2. **Zet `code-review:queued`** na een `build:pass` — niets doet dat voor je. +3. **Zet een review-stage opnieuw in de wachtrij** — haal zijn verdict weg, zet `:queued`. De build blijft. +4. **Maak een `needs-input`-stage vrij** — haal eerst zijn `:queued` weg, los de oorzaak op, zet hem dan opnieuw in de wachtrij. +5. **Zet de build opnieuw in de wachtrij** — haal het `build:*`-verdict weg, zet `build:queued`. Een volledig nieuwe build vanaf de base-branch. +6. **Handmatige fix** — als dezelfde gate blijft falen, of een mens een semantische beslissing moet nemen. -Reach voor `rebuild:queued` alleen als de oorspronkelijke build fundamenteel verkeerd was; anders verspil je goed werk. +Grijp alleen naar een nieuwe build als de oorspronkelijke build fout was; anders verspil je goed werk. `retry:queued` en `rebuild:queued` staan niet in het lijstje: geen enkele flow leest ze.
@@ -162,18 +246,20 @@ Reach voor `rebuild:queued` alleen als de oorspronkelijke build fundamenteel ver
-**2. Wat is het verschil tussen "Reviewer-labels resetten" en `retry:queued`?** +**2. Wat is het verschil tussen `needs-input` en een `:fail` op een review-stage?**
-De vraag is: **wordt de builder opnieuw aangeroepen, ja of nee?** +De vraag is: **heeft de reviewer echt een verdict opgeleverd?**
-- **Reviewer-labels resetten** (stage-labels strippen + `code-review:queued` opnieuw zetten): **de builder wordt NIET opnieuw aangeroepen**. PR + builder-output blijven exact zoals ze waren; alleen Juan en Clyde draaien opnieuw. Geschikt als de **review-contracten** zijn aangepast (nieuwe ADR, nieuwe gate-skill) en je wilt herbeoordelen zonder de code te veranderen. -- **`retry:queued`**: **builder wordt wél opnieuw aangeroepen**, in `HYDRA_MODE=fix`, met `feedback.md` (uit `hydra.json`) als input. Hij maakt nieuwe commits, dan draaien reviewers opnieuw. Geschikt als reviewers **terechte findings** hadden en de builder alleen even moet bijschaven. +- **`:fail`**: de reviewer draaide, leverde een leesbaar verdict met `hydra-gates` in `checks_run`, en zei nee. Dat is een oordeel over de code. Er wordt niets verder in de wachtrij gezet; je repareert de code (of bent het niet eens met het verdict) en zet de stage opnieuw in de wachtrij. +- **`needs-input`**: de stage leverde **geen bruikbaar verdict** — het verdict-bestand ontbrak of was onleesbaar, `checks_run` noemde geen `hydra-gates`, of de stage kon niet draaien. Dat zegt niets over de code. De stage houdt bovendien zijn `:queued`-label, dus hij draait elke tik opnieuw tot jij dat weghaalt. + +De build-baan zet nooit `needs-input`; zijn uitkomsten zijn `build:pass`, `build:fail`, `build:no-change` en `build:blocked`.
@@ -181,24 +267,24 @@ De vraag is: **wordt de builder opnieuw aangeroepen, ja of nee?**
-**3. Wanneer is het zinvoller om met de hand de fix te maken in plaats van opnieuw `retry:queued`?** +**3. Wanneer is het zinvoller om de fix met de hand te maken in plaats van nog een build?**
-Als hetzelfde patroon zich herhaalt op dezelfde mechanische gate is meer geld erin gooien niet de oplossing. +Als hetzelfde patroon zich blijft herhalen op dezelfde mechanische gate, is er meer modeltijd tegenaan gooien niet het antwoord.
-Als hetzelfde issue **3 keer of vaker** naar `needs-input` is gegaan om dezelfde reden — typisch een mechanische gate zoals `spdx-headers: fail`. Iedere `retry:queued` reproduceert hetzelfde patroon: builder maakt dezelfde fout, reviewer ziet het als boilerplate, recheck slaat rood. +Als hetzelfde issue **3 keer of vaker** om dezelfde reden gefaald is — typisch een mechanische gate zoals de SPDX-header-check. Elke nieuwe build reproduceert hetzelfde patroon. In dat geval: -- Check eerst of het een **fout-positieve gate** is (zie deel 3). Zo ja: gate-detectie aanscherpen. -- Zo nee: **fix het met de hand** (bijv. zelf SPDX-headers committen op de feature branch) en gebruik label-reset om de reviewers opnieuw te laten oordelen. +- Check eerst of het een **fout-positieve gate** is (zie deel 3). Zo ja: repareer de gate. +- Zo nee: **fix het met de hand** (bijv. zelf de SPDX-headers committen op `feature//hydra-build`) en zet de code review in de wachtrij. -Tijd: minuten. Goedkoper dan nog een Sonnet-cyclus verspillen aan iets wat steeds opnieuw fout gaat. +Tijd: minuten. Goedkoper dan nog een modelrun op iets dat steeds misgaat.
@@ -206,29 +292,29 @@ Tijd: minuten. Goedkoper dan nog een Sonnet-cyclus verspillen aan iets wat steed
-**4. Wat doe je als je 3+ identieke `needs-input`-comments ziet binnen korte tijd?** +**4. Dezelfde review-stage draait elke vijf minuten opnieuw op één issue. Wat is er aan de hand, en wat doe je?**
-Dit zegt iets over de pijplijn-zélf, niet over één issue. Welk soort fix is dat? +Waar zoekt de sequencer op, en wat blijft er op het issue staan als een stage geblokkeerd eindigt?
-**Dit is een infra-bug, geen retry-vraag.** Ergens in de completion-handler ontbreekt een terminal-state guard, waardoor een tight loop elke tick een nieuwe escalation-comment plaatst op een al-terminaal issue. Voorbeeld: 134 duplicate comments overnight op 23 april 2026 — fix was een terminal-state guard vóór alle side-effects (zie CLAUDE.md sectie "Terminal-state guards"). +De stage eindigde in `needs-input` en **hield zijn `:queued`-label**. De sequencer zoekt alleen op dat label, en hij heeft geen terminal-state guard die `needs-input`-issues overslaat, dus hij pakt de stage elke tik weer op. -**Wat doe je:** open een **infra-issue** in `hydra/`, niet een nieuwe `retry:queued`. Een retry doet niets met de comment-storm en kan zelfs meer chaos toevoegen. +**Wat doe je:** haal eerst het `:queued`-label van de stage weg, zodat de herhalingen stoppen. Zoek dan de oorzaak (geen verdict-bestand, gates niet gedraaid, stage kon niet starten), los die op, haal `needs-input` weg en zet de stage opnieuw in de wachtrij. Opnieuw in de wachtrij zetten zonder eerst het label weg te halen verandert niets; de ontbrekende guard is een infra-probleem, een issue in `hydra/` waard.
- +