Summary
Allow gate types to be configured at multiple precedence levels rather than just the per-stage `STAGE.md` field. Today `review:` is set on each stage definition. Users want to change gate behavior at the studio, intent, or per-installation level without forking studios.
Why
Right now if a user wants every `product` stage in their org to use `[external, ask]` instead of `auto`, they have to fork the studio. If they want to switch ALL stages of a single intent to `auto` for a quick prototyping pass, they have to edit each STAGE.md or live with whatever the studio shipped. Per-stage configuration is fine for the studio author; it's not enough for end-user customization.
Precedence model (highest wins)
- Per-intent override — `intent.md` frontmatter `gates:` map. Highest precedence; lets a user pin behavior for a specific intent without touching anything else.
- Per-studio user override — `.haiku/studios//stages//STAGE.md` (project-local override of a plugin-shipped studio). Already supported for studio file overlays generally; this issue makes `review:` an explicit override surface.
- Plugin studio default — `plugin/studios//stages//STAGE.md` (where it lives today).
- Compiled-in default — fall back to `auto` if nothing declares.
Proposed shape
1. `intent.md` `gates:` field
```yaml
title: Refactor billing
studio: software
mode: discrete
gates:
inception: ask
design: external
product: [external, ask]
development: ask
operations: external
security: external
```
When set, this overrides whatever the stage's STAGE.md declares for THAT intent only.
2. Resolution function
Update `resolveStageReview(studio, stage)` to also accept the intent's frontmatter:
```ts
resolveStageReview(studio, stage, intent?: IntentFrontmatter): string
```
Walk the precedence chain: `intent.gates[stage]` → `.haiku/studios//stages//STAGE.md.review` → `plugin/studios//stages//STAGE.md.review` → `"auto"`.
3. Discrete-mode interaction
The recently-shipped discrete-mode coercion (forces every stage to include `external`) STILL applies on top of the resolved gate type. So if a user pins `gates: { product: ask }` on a discrete intent, the gate becomes `ask,external` (compound) — discrete's contract isn't overridable per-stage.
Open question: should we let users opt out of discrete-mode coercion per stage? Probably no — that defeats the point of discrete mode. But worth surfacing in the docs.
4. Validation at intent creation
`haiku_intent_create` (when given an explicit `gates:` in args) validates each stage name against the studio's stage list and each value against the gate-type enum (`auto | ask | external | await | `). Reject unknown stages or types early so the user gets a clear error rather than a silent mis-route.
5. Surfacing the resolved value
Add the resolved gate type to `haiku_intent_get` and `haiku_stage_get` outputs so callers can see what's actually in effect for each stage of an intent — including whose layer won.
```json
{
"stages": [
{ "name": "product", "review": "ask,external", "review_source": "discrete-mode-coercion(intent.gates.product=ask)" }
]
}
```
Acceptance criteria
Open questions
- Should there also be a per-organization config file (`.haiku/config.yaml`) for org-wide defaults that sit between the studio and the intent? Probably future work; this issue is the per-intent + per-studio layer.
- How does `haiku:autopilot` interact? Currently autopilot promotes `ask` → `auto` at gate time. With per-intent gate configuration the user could pin `ask` and autopilot would still promote it. That's the existing behavior; should be preserved.
Summary
Allow gate types to be configured at multiple precedence levels rather than just the per-stage `STAGE.md` field. Today `review:` is set on each stage definition. Users want to change gate behavior at the studio, intent, or per-installation level without forking studios.
Why
Right now if a user wants every `product` stage in their org to use `[external, ask]` instead of `auto`, they have to fork the studio. If they want to switch ALL stages of a single intent to `auto` for a quick prototyping pass, they have to edit each STAGE.md or live with whatever the studio shipped. Per-stage configuration is fine for the studio author; it's not enough for end-user customization.
Precedence model (highest wins)
Proposed shape
1. `intent.md` `gates:` field
```yaml
title: Refactor billing
studio: software
mode: discrete
gates:
inception: ask
design: external
product: [external, ask]
development: ask
operations: external
security: external
```
When set, this overrides whatever the stage's STAGE.md declares for THAT intent only.
2. Resolution function
Update `resolveStageReview(studio, stage)` to also accept the intent's frontmatter:
```ts
resolveStageReview(studio, stage, intent?: IntentFrontmatter): string
```
Walk the precedence chain: `intent.gates[stage]` → `.haiku/studios//stages//STAGE.md.review` → `plugin/studios//stages//STAGE.md.review` → `"auto"`.
3. Discrete-mode interaction
The recently-shipped discrete-mode coercion (forces every stage to include `external`) STILL applies on top of the resolved gate type. So if a user pins `gates: { product: ask }` on a discrete intent, the gate becomes `ask,external` (compound) — discrete's contract isn't overridable per-stage.
Open question: should we let users opt out of discrete-mode coercion per stage? Probably no — that defeats the point of discrete mode. But worth surfacing in the docs.
4. Validation at intent creation
`haiku_intent_create` (when given an explicit `gates:` in args) validates each stage name against the studio's stage list and each value against the gate-type enum (`auto | ask | external | await | `). Reject unknown stages or types early so the user gets a clear error rather than a silent mis-route.
5. Surfacing the resolved value
Add the resolved gate type to `haiku_intent_get` and `haiku_stage_get` outputs so callers can see what's actually in effect for each stage of an intent — including whose layer won.
```json
{
"stages": [
{ "name": "product", "review": "ask,external", "review_source": "discrete-mode-coercion(intent.gates.product=ask)" }
]
}
```
Acceptance criteria
Open questions