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

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 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, 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:


# .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 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:

// 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 (and mirrored in codex.go, 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 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. 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:

// 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 controls which binary and arguments are used for each agent type.
  • Factory pattern in 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 file to any supported identifier such as claude, codex, pi, opencode, acpx, or rovodev. The agent.New() factory in 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 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 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.

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 →