# How the Gate Model in no-mistakes Works: Pause-and-Resume Pipeline Mechanics

> Discover the gate model in no-mistakes. Learn how it pauses pipeline execution for intervention and resumes automatically after approval, ensuring no data loss.

- Repository: [Kun Chen/no-mistakes](https://github.com/kunchenguid/no-mistakes)
- Tags: internals
- Published: 2026-07-03

---

**The gate model in no-mistakes pauses pipeline execution when a step requires human or agent intervention, persisting an awaiting-agent marker in the database and surfacing an interactive CLI prompt for approval before resuming.**

The `no-mistakes` repository implements a sophisticated pipeline orchestration system that uses a **gate model** to manage human-in-the-loop workflows. When automated steps encounter situations requiring judgment calls—such as lint failures or merge conflicts—the system gracefully pauses execution and surfaces an interactive interface for agents to approve, reject, or modify the proposed changes. This article examines the internal implementation of the gate model in no-mistakes, tracing how the executor coordinates between pipeline steps, database persistence, and CLI interactions.

## Core Flow of the Gate Model

The gate model operates as a coordinated handshake between pipeline steps, the executor, and the database. When a step reaches a decision point, it signals the executor to enter a paused state that survives process restarts.

### Step Execution and Gate Creation

Each pipeline step lives in `internal/pipeline/steps` and calls `gate.New()` when it requires external input. The constructor accepts a **type** (e.g., `gate.TypeAwaitingApproval`), a **message** for the agent, and optional **metadata** such as suggested fixes.

In [`internal/pipeline/steps/lint.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/pipeline/steps/lint.go), a step might create a gate like this:

```go
func (s *LintStep) Run(ctx context.Context, run *db.Run) error {
    // ... run lint ...
    if issuesFound {
        g := gate.New(gate.TypeAwaitingApproval,
            "Lint found problems – approve to continue or abort.",
            gate.WithMetadata(map[string]string{"details": details}))
        return s.Gate(g)
    }
    return nil
}

```

Returning the gate from the step signals the executor to pause rather than proceed to the next step.

### Gate Registration and State Persistence

The executor in [`internal/pipeline/executor.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/pipeline/executor.go) receives the gate and immediately writes an **awaiting-agent flag** to the database via `db.SetRunAwaitingAgent(runID, true)`. This function writes a non-null `awaiting_agent_since` timestamp to the `runs` table, creating a durable marker that any process can query.

Simultaneously, the executor stores the gate struct in the run’s in-memory state (`run.gate`), allowing the system to reference the gate’s metadata while rendering CLI output.

### Agent Interaction and Rendering

While paused, the run surfaces its status through the `no-mistakes axi` CLI command, implemented in [`internal/cli/axi_render.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/cli/axi_render.go). The output includes an `awaiting_agent: parked <duration>` field that updates in real-time using the injectable `nowUnix` variable, which keeps durations deterministic during tests.

```bash
$ no-mistakes axi status

# shows "awaiting_agent: parked 12s"

```

### Gate Resolution and Pipeline Resumption

When an agent responds via `no-mistakes axi respond yes`, the executor’s `resolveGate` function clears the database flag using `db.ClearRunAwaitingAgent(runID)`, removes the gate from the run’s state, and allows the pipeline to proceed:

```go
func (e *Executor) resolveGate(runID int64, decision gate.Decision) {
    db.ClearRunAwaitingAgent(runID) // removes the parked marker
    // Continue pipeline processing ...
}

```

## Safety Mechanisms and Crash Recovery

The gate model includes safeguards against stale state. The **awaiting-agent flag** is automatically cleared during crash recovery via `executor.RecoverStaleRuns()`, ensuring that runs never appear artificially parked after a system restart. Additionally, the gate model enforces a **read-only** boundary for external code—only the executor may set or clear the awaiting flag, preventing accidental state corruption by pipeline steps or CLI commands.

## Key Implementation Files

| Package / File | Responsibility |
|---|---|
| [`internal/gate/gate.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/gate/gate.go) | Defines the `Gate` struct and helper constructors like `gate.New()`. |
| [`internal/pipeline/executor.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/pipeline/executor.go) | Orchestrates run lifecycles, handles gate registration via `handleGate()`, and manages resolution through `waitForApproval()` and `resolveGate()`. |
| [`internal/db/run.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/db/run.go) | Provides database helpers `SetRunAwaitingAgent()` and `ClearRunAwaitingAgent()` for the `awaiting_agent_since` timestamp. |
| [`internal/cli/axi_render.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/cli/axi_render.go) | Renders gate objects for the CLI, including parked duration calculations using `nowUnix`. |
| [`docs/src/content/docs/concepts/gate-model.md`](https://github.com/kunchenguid/no-mistakes/blob/main/docs/src/content/docs/concepts/gate-model.md) | Documents the conceptual architecture of the gate model. |

## Practical Code Examples

### Creating a Gate Inside a Step

```go
// internal/pipeline/steps/lint.go (simplified)
func (s *LintStep) Run(ctx context.Context, run *db.Run) error {
    if issuesFound {
        g := gate.New(gate.TypeAwaitingApproval,
            "Lint found problems – approve to continue or abort.",
            gate.WithMetadata(map[string]string{"details": details}))
        return s.Gate(g)
    }
    return nil
}

```

### Handling the Gate in the Executor

```go
// internal/pipeline/executor.go (excerpt)
func (e *Executor) handleGate(g *gate.Gate, runID int64) error {
    if err := db.SetRunAwaitingAgent(runID, true); err != nil {
        return err
    }
    e.runState[runID].gate = g
    return e.waitForApproval(runID, g)
}

```

### Resolving via the CLI

```bash
$ no-mistakes axi respond yes  # approves the gate

```

Internally, this triggers the resolution path:

```go
func (e *Executor) resolveGate(runID int64, decision gate.Decision) {
    db.ClearRunAwaitingAgent(runID)
    // Resume execution...
}

```

## Summary

- The **gate model** pauses pipeline execution by creating a durable database marker via `db.SetRunAwaitingAgent()`.
- Gates are instantiated using `gate.New()` in pipeline steps and handled by the executor in [`internal/pipeline/executor.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/pipeline/executor.go).
- The CLI renders gate status with real-time parked durations through [`internal/cli/axi_render.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/cli/axi_render.go).
- Resolution clears the flag via `db.ClearRunAwaitingAgent()` and resumes the pipeline.
- Crash recovery automatically clears stale awaiting-agent flags to prevent infinite pauses.

## Frequently Asked Questions

### What triggers a gate in no-mistakes?

A gate triggers when any pipeline step encounters a condition requiring human judgment—such as lint failures, test mismatches, or merge conflicts—and explicitly returns a `gate.Gate` object constructed via `gate.New()` to the executor.

### How does no-mistakes handle crashes during a gated pause?

The system clears the `awaiting_agent_since` flag during startup via `executor.RecoverStaleRuns()`, ensuring that runs interrupted by crashes do not remain in a false parked state when the service restarts.

### Can multiple gates be active simultaneously in a single run?

No, the current implementation stores a single gate per run in `e.runState[runID].gate`, meaning the pipeline processes gates sequentially; a new gate cannot be created until the previous one resolves.

### What happens to the database state when a gate is resolved?

The executor calls `db.ClearRunAwaitingAgent(runID)`, which sets the `awaiting_agent_since` column to null, effectively removing the parked marker and allowing the run to proceed to subsequent steps.