How the no-mistakes Pipeline Handles Parked Runs Awaiting Agent Approval

The no-mistakes pipeline marks runs as parked by writing a Unix timestamp to the awaiting_agent_since column in SQLite, clears the marker after the agent responds via axi respond, and exposes this state through IPC fields and CLI output for observable, crash-resistant automation.

The kunchenguid/no-mistakes repository implements a durable approval-gate mechanism that pauses pipeline execution when steps require human or automated agent verification. When a run reaches an awaiting_approval or fix_review gate, the system transitions into a parked state that persists in the database, allowing external tools to poll status without maintaining active connections to the executor.

Database Schema for the Parked State

The parked signal relies on a single nullable column in the runs table. The awaiting_agent_since field stores the current Unix-seconds timestamp when a run is parked, and remains NULL during normal execution. This schema design allows atomic updates and provides a natural way to calculate wait duration by subtracting the stored timestamp from the current time.

Executor Flow: Setting and Clearing Parked Markers

The coordinator inside internal/pipeline/executor.go manages the lifecycle of the parked state around blocking approval waits.

Entering the Approval Gate

Before blocking for agent input, the executor marks the run as parked. This ensures that even if the CLI queries status milliseconds later, it sees the awaiting state immediately:

// internal/pipeline/executor.go
if dbErr := e.db.SetRunAwaitingAgent(run.ID); dbErr != nil {
    slog.Warn("failed to set awaiting-agent marker in db", …)
}

The SetRunAwaitingAgent function defined in internal/db/run.go executes an atomic UPDATE statement:

// internal/db/run.go
func (d *DB) SetRunAwaitingAgent(id string) error {
    ts := now()
    _, err := d.sql.Exec(`UPDATE runs SET awaiting_agent_since = ?, updated_at = ? WHERE id = ?`,
        ts, ts, id)
    …
}

Blocking for Agent Response

Immediately after setting the marker, the executor invokes e.waitForApproval, which blocks until the agent sends an axi respond command, the context is cancelled, or an error occurs. While blocked, the run remains parked in the database, allowing external observers to query how long the run has waited without accessing executor memory.

Exiting the Gate

Once waitForApproval returns, the executor clears the parked marker regardless of the outcome. This guarantees that the run never remains marked as awaiting an agent after the gate resolves:

// internal/pipeline/executor.go (after wait returns)
if dbErr := e.db.ClearRunAwaitingAgent(run.ID); dbErr != nil {
    slog.Warn("failed to clear awaiting-agent marker in db", …)
}

The corresponding database function sets the column back to NULL:

// internal/db/run.go
func (d *DB) ClearRunAwaitingAgent(id string) error {
    _, err := d.sql.Exec(`UPDATE runs SET awaiting_agent_since = NULL, updated_at = ? WHERE id = ?`,
        now(), id)
    …
}

IPC Protocol Exposure

The IPC layer exposes the parked state to external clients through the RunInfo struct in internal/ipc/protocol.go. This structure mirrors the database schema exactly:

// internal/ipc/protocol.go
AwaitingAgent      bool   `json:"awaiting_agent,omitempty"`
AwaitingAgentSince *int64 `json:"awaiting_agent_since,omitempty"`

When the daemon broadcasts run_updated events, these fields populate from the current database row. Any client—including the axi status CLI—can determine the parked state and duration with a single RPC call without polling the executor's internal state machine.

CLI Rendering of Parked Status

The command-line interface translates the raw timestamp into human-readable output. In internal/cli/axi_render.go, the formatter appends an awaiting_agent field only when the run is parked and not in a terminal state:

// internal/cli/axi_render.go
if rv.AwaitingAgentSince != nil && !terminalStatus(rv.Status) {
    fields = append(fields, toon.Field{
        Key: "awaiting_agent",
        Value: formatParkedFor(*rv.AwaitingAgentSince),
    })
}

The formatParkedFor helper converts the Unix timestamp into a duration string such as "parked 2m30s", making the signal instantly visible to operators monitoring pipeline status.

Crash Recovery and Stale State Cleanup

To prevent false positives after daemon restarts, the RecoverStaleRuns function in internal/db/run.go executes on startup. This routine clears any lingering awaiting_agent_since values for runs that are being marked failed, ensuring that a crashed daemon never reports a completed or dead run as still parked and awaiting input.

Summary

  • Database marker: The awaiting_agent_since column stores a Unix timestamp when parked and NULL otherwise, enabling atomic state transitions in internal/db/run.go.
  • Executor coordination: SetRunAwaitingAgent and ClearRunAwaitingAgent bracket the blocking waitForApproval call in internal/pipeline/executor.go.
  • External visibility: The IPC protocol exposes AwaitingAgent and AwaitingAgentSince fields in internal/ipc/protocol.go for poll-based observation.
  • User experience: The CLI renders human-readable parked durations via formatParkedFor in internal/cli/axi_render.go.
  • Crash safety: RecoverStaleRuns cleans up stale markers on daemon startup to prevent phantom parked states.

Frequently Asked Questions

How does the no-mistakes pipeline detect if a run is currently parked?

External tools query the RunInfo struct via the IPC protocol. If the AwaitingAgentSince field is non-nil and the status is non-terminal, the run is parked. The CLI performs this check in internal/cli/axi_render.go before displaying the "awaiting_agent" field.

What happens to the parked state if the daemon crashes while a run is awaiting approval?

On startup, the daemon invokes RecoverStaleRuns in internal/db/run.go, which clears awaiting_agent_since for any runs being processed. This prevents the system from reporting a run as parked when it is actually failed or completed, ensuring consistency between the database and executor state.

How long can a run remain in the parked state?

The pipeline imposes no hard timeout on the parked state itself; the run remains parked indefinitely until the agent sends an axi respond command or the context is explicitly cancelled. Operators can monitor wait duration by checking the AwaitingAgentSince timestamp, which exposes how many seconds the run has awaited approval.

Where does the CLI get the information to display "parked 2m30s"?

The axi status command receives a RunInfo message from the daemon via IPC defined in internal/ipc/protocol.go. The formatter in internal/cli/axi_render.go passes the AwaitingAgentSince timestamp to formatParkedFor, which calculates the delta between the stored time and current time, rendering it as a human-readable duration string.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →