# How no-mistakes Configures and Manages Agent Spawning Across Different Entry Points

> Discover how no-mistakes configures and manages agent spawning via a YAML-driven factory, ensuring isolated processes, automatic retries, and JSONL output for LLM integrations.

- Repository: [Kun Chen/no-mistakes](https://github.com/kunchenguid/no-mistakes)
- Tags: how-to-guide
- Published: 2026-06-25

---

**no-mistakes treats every LLM-backed agent as a configurable plugin binary, using a YAML-driven factory pattern to spawn processes with isolated process groups, automatic retry logic, and streaming JSONL output parsing.**

The `no-mistakes` repository provides a unified framework for orchestrating LLM agents like Claude, Codex, and Open-Code through a declarative configuration system. By treating each agent as a pluggable binary with standardized entry points, the tool enables seamless switching between different AI providers while maintaining consistent process management and error handling across all pipeline steps.

## Configuration-Driven Agent Selection

Agent spawning in `no-mistakes` begins with the [`.no-mistakes.yaml`](https://github.com/kunchenguid/no-mistakes/blob/main/.no-mistakes.yaml) configuration file. The `agent:` section allows users to specify which binary to invoke and what arguments to pass. According to the source code in [`internal/config/config.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/config/config.go), the configuration is parsed into a `config.Agent` struct that exposes two critical fields: `bin` for the binary path and `extraArgs` for CLI overrides.

Users can override the default binary location and inject custom flags without modifying the core logic:

```yaml

# .no-mistakes.yaml

agent:
  name: claude
  bin: /usr/local/bin/claude-cli          # custom binary location

  args_override:
    - "--model=claude-3.5-sonnet"
    - "--temperature=0.2"

```

The `config.LoadEffective()` function merges repository-specific settings with global defaults, ensuring that the `agent.New()` factory receives a complete configuration object regardless of which pipeline step triggers the spawn.

## The Agent Factory and Registry Pattern

The [`internal/agent/registry.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/agent/registry.go) file implements a factory that maps string identifiers to concrete agent implementations. When a pipeline step requires an LLM, it calls `agent.New(name, cfg)`, where `name` corresponds to supported agents like `claude`, `codex`, `pi`, `opencode`, `acpx`, or `rovodev`.

Each concrete type—such as `claudeAgent` or `codexAgent`—satisfies the `agent.Agent` interface, which mandates two methods: `Run(context.Context, RunOpts) (*Result, error)` and `Close() error`. This abstraction allows the pipeline steps in `internal/pipeline/steps/` to remain agnostic about which specific LLM backend is processing the request.

The following example demonstrates how the pipeline creates and invokes an agent:

```go
// Load the global config (already done by the CLI)
cfg := config.LoadEffective()           // internal/config/config.go

// The pipeline step wants the “claude” agent
agent := agent.New("claude", cfg.Agent) // internal/agent/registry.go

// Run the agent with a prompt and a JSON schema
result, err := agent.Run(ctx, agent.RunOpts{
    Prompt:     "Fix the bug in this file …",
    JSONSchema: schemaBytes,
    CWD:        workdir,
    OnChunk:    func(chunk string) { fmt.Print(chunk) },
})
if err != nil {
    log.Fatalf("agent failed: %v", err)
}
fmt.Printf("Structured result: %s\n", result.Output)

```

## Spawning Processes with Entry Point Overrides

Inside [`internal/agent/claude.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/agent/claude.go) (and mirrored in [`codex.go`](https://github.com/kunchenguid/no-mistakes/blob/main/codex.go), [`pi.go`](https://github.com/kunchenguid/no-mistakes/blob/main/pi.go), etc.), the `runOnce` method handles the actual process creation. This method builds the command line using `buildArgs`, which prepends user-supplied `agent_args_override` to automatically generated flags, ensuring that user preferences take precedence in conflicts.

The spawn logic uses `exec.CommandContext` with a working directory (`opts.CWD`) and a safe environment (`gitSafeEnv`). Critically, all agents invoke `shellenv.ConfigureShellCommand` from [`internal/shellenv/shellenv.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/shellenv/shellenv.go) before execution. This utility places the child process in its own process group, guaranteeing that context cancellation kills the entire subprocess tree—including any spawned git commands or editors—preventing orphaned processes from locking the worktree.

## Resilient Execution with Automatic Retries

Rather than failing immediately on transient errors, `no-mistakes` implements a shared retry mechanism in [`internal/agent/retry.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/agent/retry.go). The `runWithRetry` helper accepts a `maxRetries` constant and an error classifier function specific to each agent type.

Each agent file declares its own retry limit, such as `claudeMaxRetries = 3`. The retry wrapper distinguishes between transient failures (worth retrying) and fatal errors (immediate termination), while optionally invoking an `onRetry` callback for logging or UI updates:

```go
// Simplified from internal/agent/retry.go
func runWithRetry(
    ctx context.Context,
    name string,
    opts RunOpts,
    maxRetries int,
    classifier func(error) bool,
    onRetry func(int, error),
    runOnce func() (*Result, error),
) (*Result, error) {
    var lastErr error
    for i := 0; i <= maxRetries; i++ {
        res, err := runOnce()
        if err == nil {
            return res, nil
        }
        lastErr = err
        if !classifier(err) {               // not a transient error → give up
            break
        }
        if onRetry != nil {
            onRetry(i, err)
        }
        // back‑off could be added here
    }
    return nil, fmt.Errorf("%s failed after %d attempts: %w", name, maxRetries+1, lastErr)
}

```

## Streaming Output and Result Finalization

Once spawned, agents communicate via a streaming JSONL protocol. The `parseClaudeEvents` function (and equivalents like `parseCodexEvents`) reads stdout line-by-line, parsing each JSON event and passing it to the `RunOpts.OnChunk` callback. This architecture allows the UI to display incremental results while the agent is still running.

When the stream terminates, `finalizeClaudeResult` validates that the structured output matches the requested JSON schema, extracts token usage metrics, and returns a populated `Result` struct to the caller. The `Close()` method on each agent currently serves as a no-op since processes terminate naturally, but it satisfies the interface for future resource-release logic.

## Summary

- **Declarative configuration** in [`.no-mistakes.yaml`](https://github.com/kunchenguid/no-mistakes/blob/main/.no-mistakes.yaml) controls which binary and arguments are used for each agent type.
- **Factory pattern** in [`registry.go`](https://github.com/kunchenguid/no-mistakes/blob/main/registry.go) maps friendly names like `claude` or `codex` to concrete implementations without exposing backend details to pipeline steps.
- **Process isolation** via `ConfigureShellCommand` ensures clean subprocess tree management and prevents orphaned processes.
- **Resilient execution** through `runWithRetry` provides per-agent retry policies with configurable error classification.
- **Streaming architecture** parses JSONL events in real-time, enabling progressive UI updates before final schema validation.

## Frequently Asked Questions

### How do I switch between different LLM agents in no-mistakes?

Change the `agent.name` field in your [`.no-mistakes.yaml`](https://github.com/kunchenguid/no-mistakes/blob/main/.no-mistakes.yaml) file to any supported identifier such as `claude`, `codex`, `pi`, `opencode`, `acpx`, or `rovodev`. The `agent.New()` factory in [`internal/agent/registry.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/agent/registry.go) automatically instantiates the correct concrete type based on this string, requiring no changes to your pipeline steps.

### What happens when an agent process fails or times out?

The `runWithRetry` mechanism in [`internal/agent/retry.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/agent/retry.go) intercepts the error and checks it against an agent-specific classifier function (e.g., `claudeRetryClassifier`). Transient errors trigger a retry up to the per-agent limit (typically 3 attempts), while fatal errors terminate immediately. Process-group isolation ensures that cancellation or timeouts kill the entire subprocess tree, not just the parent binary.

### How does no-mistakes prevent orphaned subprocesses from hanging?

All agent implementations call `shellenv.ConfigureShellCommand` before spawning, which places the child process in its own process group. When the context is cancelled or the parent process exits, the entire group—including any spawned git commands, editors, or helper scripts—is terminated together. This prevents the common issue of background processes locking the working directory after the main agent exits.

### Can I pass custom arguments to the agent binary?

Yes. The `agent.args_override` array in [`.no-mistakes.yaml`](https://github.com/kunchenguid/no-mistakes/blob/main/.no-mistakes.yaml) allows you to prepend arbitrary flags to the command line. The `buildArgs` method in each agent implementation (such as `claudeAgent`) prepends these user arguments before the auto-generated ones, ensuring that your overrides take precedence when conflicts occur.