# How the no-mistakes Gate Model Works Internally: Pipeline Pausing and Agent Resolution

> Discover the internal workings of the no-mistakes gate model. Learn how it pauses CI/CD pipelines, uses agent resolution, and ensures error-free deployments via interactive CLI prompts.

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

---

**The no-mistakes gate model pauses CI/CD pipeline execution by instantiating a Gate struct that sets an awaiting-agent flag in the database, surfaces an interactive CLI prompt via the `axi` command, and blocks until an external agent approves, rejects, or modifies the pending step.**

The `kunchenguid/no-mistakes` repository implements a deterministic gate mechanism that allows automated pipelines to safely halt for human or AI intervention. When a step requires external judgment—such as approving lint failures or reviewing rebases—the system creates a gate that persists state across process restarts and renders real-time status updates. Understanding this internal flow reveals how the tool balances automation with safety-critical manual checkpoints.

## Core Components of the Gate Model

### Gate Data Structure and Constructors

At the heart of the system lies the `Gate` struct defined in [`internal/gate/gate.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/gate/gate.go). This immutable data structure captures the gate type, display message, and optional metadata suggested fixes. Steps instantiate gates using `gate.New()`, passing enums like `gate.TypeAwaitingApproval` to classify the intervention required.

The constructor supports functional options such as `gate.WithMetadata()` for attaching structured context. Once created, the gate becomes the canonical token representing the paused state, passed between the pipeline executor and the database layer.

### Database Persistence and the Awaiting-Agent Flag

The gate model achieves durability through the `runs` table managed in [`internal/db/run.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/db/run.go). When a step invokes a gate, the executor calls `db.SetRunAwaitingAgent(runID, true)`, which writes a non-null `awaiting_agent_since` timestamp. This column acts as the global semaphore indicating that a run is parked awaiting external resolution.

The timestamp serves dual purposes: it flags the awaiting state for external observers (like the `axi status` command) and provides the baseline for calculating parked duration. The companion function `db.ClearRunAwaitingAgent(runID)` nullifies this field upon resolution, atomically releasing the run to continue execution.

### Executor Orchestration and State Management

The `Executor` type in [`internal/pipeline/executor.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/pipeline/executor.go) owns the gate lifecycle. It maintains an in-memory cache mapping `runID` to gate objects via `e.runState[runID].gate`, allowing the process to reference the pending decision without repeated database queries.

The executor implements `handleGate()` to transition into the paused state and `waitForApproval()` to block the pipeline goroutine. Resolution arrives via `resolveGate(runID, decision)`, which coordinates the database clearance and pipeline resumption. Additionally, `executor.RecoverStaleRuns()` automatically clears awaiting-agent flags for runs that crashed while gated, preventing phantom parked states.

## Step-by-Step Execution Flow

The interaction between pipeline steps, the executor, and the agent follows a strict protocol:

1. **Step Detection** – A step implementation in `internal/pipeline/steps` detects a condition requiring intervention (e.g., lint errors) and constructs a `Gate` using `gate.New(gate.TypeAwaitingApproval, message, opts...)`.

2. **Gate Registration** – The step returns the gate to the executor, triggering `handleGate()` which immediately invokes `db.SetRunAwaitingAgent(runID, true)` to persist the awaiting-agent timestamp.

3. **State Persistence** – The executor stores the gate struct in its internal run state while the database write ensures visibility to other processes, including CLI tooling and potential distributed observers.

4. **Interactive Rendering** – The `axi` CLI command (implemented in [`internal/cli/axi_render.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/cli/axi_render.go)) queries the database, detects the non-null `awaiting_agent_since` field, and displays `awaiting_agent: parked <duration>`. The duration calculation uses an injectable `nowUnix` variable to ensure deterministic test output.

5. **Agent Resolution** – An operator or automated agent runs `no-mistakes axi respond yes` (or similar), which sends a resolution message to the executor. The executor validates the decision and invokes `resolveGate()`.

6. **Cleanup and Resumption** – Inside `resolveGate()`, the executor calls `db.ClearRunAwaitingAgent(runID)` to remove the parked marker, deletes the gate from in-memory state, and unblocks the pipeline to execute subsequent steps.

## Safety Mechanisms and Concurrency Guarantees

The gate model implements defensive programming to prevent stuck pipelines and state corruption:

- **Crash Recovery** – On startup, `executor.RecoverStaleRuns()` scans for runs with orphaned `awaiting_agent_since` timestamps where the coordinating process died. It automatically clears these flags, ensuring runs never remain artificially parked.

- **Read-Only External Interface** – External code can inspect gate status through the CLI and database views, but only the executor may modify the awaiting-agent flag. This prevents accidental state corruption from external scripts or API consumers.

- **Deterministic Time Tracking** – The `axi` renderer accepts a `nowUnix` injection point rather than calling `time.Now()` directly. This design enables reproducible tests that verify duration calculations without flaky timing assertions.

## Code Examples

### Creating a Gate Inside a Pipeline Step

When linting detects issues, the step constructs a gate and signals the executor to pause:

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

```

### Executor Handling the Gate Pause

The executor receives the gate, persists the awaiting-agent flag, and blocks:

```go
// internal/pipeline/executor.go (simplified excerpt)
func (e *Executor) handleGate(g *gate.Gate, runID int64) error {
    // Atomic write to database marks run as parked
    if err := db.SetRunAwaitingAgent(runID, true); err != nil {
        return err
    }
    // Cache gate for later reference
    e.runState[runID].gate = g
    // Block until agent responds
    return e.waitForApproval(runID, g)
}

```

### Resolving a Gate via CLI Interaction

An agent interacts with the paused pipeline through the `axi` subcommand:

```bash

# Check current status showing parked duration

$ no-mistakes axi status
Project: my-service
Run ID: 42
Status: awaiting_agent: parked 12s

# Approve and continue execution

$ no-mistakes axi respond yes

```

Internally, the resolution triggers database cleanup:

```go
// internal/pipeline/executor.go
func (e *Executor) resolveGate(runID int64, decision gate.Decision) {
    // Remove the awaiting-agent timestamp
    db.ClearRunAwaitingAgent(runID)
    // Remove from memory and resume pipeline
    delete(e.runState[runID], "gate")
    // Continue to next step...
}

```

## Summary

- The **no-mistakes gate model** creates a persistent pause point in CI/CD pipelines by instantiating a `Gate` struct and setting an `awaiting_agent_since` timestamp in the database via [`internal/db/run.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/db/run.go).
- Only the **executor** in [`internal/pipeline/executor.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/pipeline/executor.go) can modify the awaiting-agent flag, ensuring state integrity while external tools remain read-only.
- The **CLI renderer** in [`internal/cli/axi_render.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/cli/axi_render.go) displays real-time parked durations using deterministic time injection, enabling both human and automated agents to observe and resolve gates.
- **Crash recovery** automatically clears stale flags during startup, preventing runs from becoming permanently stuck in an awaiting-agent state.
- Resolution requires explicit agent action through `no-mistakes axi respond`, which triggers `db.ClearRunAwaitingAgent()` and resumes pipeline execution.

## Frequently Asked Questions

### What triggers the creation of a gate in no-mistakes?

A gate is created when any pipeline step determines it needs external validation before proceeding. According to the source code in `internal/pipeline/steps`, steps such as lint or rebase call `gate.New()` with types like `TypeAwaitingApproval` when they encounter conditions that require human judgment or automated policy checks, returning the gate object to the executor to initiate the pause.

### How does the executor prevent runs from staying paused indefinitely after a crash?

The `executor.RecoverStaleRuns()` function runs during process initialization and scans the database for runs with a non-null `awaiting_agent_since` timestamp that are no longer associated with a live executor process. It automatically invokes `db.ClearRunAwaitingAgent()` on these orphaned entries, ensuring that crashed or restarted services do not leave pipelines artificially blocked.

### Can external automation bypass the CLI and resolve gates directly?

No, the gate model enforces a **read-only external interface**. While external tools can query the awaiting status through database views or the `axi status` command, only the internal executor's `resolveGate()` method can call `db.ClearRunAwaitingAgent()`. This design prevents race conditions and ensures that state transitions occur only through the validated `no-mistakes axi respond` code path.

### Why does the parked duration display use an injectable `nowUnix` variable instead of system time?

The `axi` renderer in [`internal/cli/axi_render.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/cli/axi_render.go) accepts an injectable `nowUnix` parameter to calculate the `parked <duration>` string. This dependency injection pattern allows unit tests to pass a frozen timestamp, verifying that duration calculations and formatting logic behave correctly without relying on flaky system clock assertions or `time.Sleep()` calls in test suites.