Skip to content

Per-studio / per-stage gate type configuration with override semantics #237

Description

@jwaldrip

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)

  1. Per-intent override — `intent.md` frontmatter `gates:` map. Highest precedence; lets a user pin behavior for a specific intent without touching anything else.
  2. 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.
  3. Plugin studio default — `plugin/studios//stages//STAGE.md` (where it lives today).
  4. 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

  • `intent.md` accepts a `gates:` map; values validated at intent creation
  • `resolveStageReview` walks the precedence chain
  • Per-studio user overrides at `.haiku/studios//...` override plugin defaults
  • Resolved gate type + source layer exposed via `haiku_intent_get`
  • Tests cover: intent override beats studio override beats plugin default; discrete-mode coercion stacks on top; invalid stage name in `gates:` rejected
  • Documentation in `website/content/docs/` covers the precedence model with examples

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions