# How Native Agent Runners Implement the Agent Interface and SSE Streaming in No-Mistakes

> Discover how native agent runners in no-mistakes implement the Agent interface and SSE streaming by launching vendor CLIs and parsing JSON streams for real-time output.

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

---

**Native agent runners in no-mistakes satisfy the `Agent` interface by launching vendor CLI binaries and parsing line-delimited JSON streams to forward partial output in real time via the `RunOpts.OnChunk` callback.**

The `no-mistakes` repository provides a unified abstraction for AI-driven automation through a minimal Go interface. Native agent runners for Claude and Copilot implement this contract by wrapping vendor-provided CLI tools, enabling Server-Sent Events (SSE)-style streaming of AI-generated content without buffering entire responses.

## The Agent Interface Contract

At [`internal/agent/agent.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/agent/agent.go), the repository defines the core abstraction that every AI-backed runner must satisfy:

```go
type Agent interface {
    Name() string
    Run(context.Context, RunOpts) (*Result, error)
    Close() error
}

```

The `RunOpts` struct includes an `OnChunk` callback function that enables streaming. When provided, native agents invoke this callback immediately upon receiving partial text from the underlying CLI process, allowing the UI to display results incrementally rather than waiting for the complete response.

## Claude and Copilot Implementation Patterns

Both native agents follow an identical architectural pattern while adapting to their respective CLI formats. They implement the `Agent` interface through concrete structs that manage binary paths and execution arguments.

### Struct Definition and Identity

In [`internal/agent/claude.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/agent/claude.go), the Claude runner defines a struct that holds the binary path and extra arguments:

```go
type claudeAgent struct {
    bin       string
    extraArgs []string
}

```

The `Name()` method returns a constant identifier used for logging and configuration:

```go
func (a *claudeAgent) Name() string { return "claude" }

```

Similarly, in [`internal/agent/copilot.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/agent/copilot.go), the Copilot runner mirrors this structure with its own distinct name:

```go
type copilotAgent struct {
    bin       string
    extraArgs []string
}

func (a *copilotAgent) Name() string { return "copilot" }

```

### Process Execution with Retry Logic

Both implementations delegate execution to a shared retry wrapper defined in [`internal/agent/native_command.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/agent/native_command.go). The `Run()` method calls `runWithRetry()`, which attempts the operation up to `claudeMaxRetries` (currently 3) before surfacing transient failures like network blips or rate limits.

The `runOnce()` method handles the actual CLI invocation. For Claude, the implementation builds arguments and configures the command context:

```go
func (a *claudeAgent) runOnce(ctx context.Context, opts RunOpts) (*Result, error) {
    args := a.buildArgs(opts.Prompt, opts.JSONSchema)
    cmd := exec.CommandContext(ctx, a.bin, args...)
    cmd.Dir = opts.CWD
    cmd.Env = gitSafeEnv(opts.CWD)
    shellenv.ConfigureShellCommand(cmd)
    
    started, err := startNativeAgentCommand(cmd)
    // ... error handling and parsing
    if err := parseClaudeEvents(ctx, started.stdout, opts.OnChunk, &usage, &result); err != nil {
        // ...
    }
    return finalizeClaudeResult(result, opts.JSONSchema, usage)
}

```

Copilot follows the identical pattern in [`internal/agent/copilot.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/agent/copilot.go), using `buildCopilotPrompt()` to construct the input and `buildArgs()` to assemble command-line flags.

### Process Isolation and Pipe Management

The `startNativeAgentCommand()` helper in [`internal/agent/native_command.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/agent/native_command.go) wraps `exec.Cmd` with proper pipe management, returning separate `stdout` and `stderr` readers along with a `wait` method. This ensures that error messages are captured without blocking the streaming output.

Both agents utilize `shellenv.ConfigureShellCommand()` from the `internal/shellenv/` package to place the spawned process in its own process group. This guarantees that child processes are properly terminated when the context is cancelled, preventing orphaned CLI processes.

## SSE Streaming Implementation

Native agents implement Server-Sent Events-style streaming by parsing the CLI's line-oriented JSON output incrementally. Rather than buffering the entire response, they process each line as it arrives and forward partial content through the `OnChunk` callback.

### Claude's Stream-JSON Parsing

The Claude CLI emits events in a line-delimited JSON format (`--output-format stream-json`). The `parseClaudeEvents()` function in [`internal/agent/claude.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/agent/claude.go) consumes this stream using a `bufio.Scanner`:

```go
func parseClaudeEvents(
    ctx context.Context,
    r io.Reader,
    onChunk func(string),
    usage *TokenUsage,
    result **claudeResult,
) error {
    scanner := bufio.NewScanner(r)
    for scanner.Scan() {
        // Parse JSON event
        switch event.Type {
        case "assistant":
            if onChunk != nil {
                onChunk(event.Text)
            }
        case "result":
            // Capture final result event
        }
    }
    return scanner.Err()
}

```

Each `assistant` event appends text to a local buffer and immediately invokes `onChunk` if configured, providing real-time updates to the caller. The final `result` event is stored for later conversion via `finalizeClaudeResult()`.

### Copilot's JSONL Event Handling

Copilot uses a similar JSON Lines format but with distinct event types. The `parseCopilotEvents()` function in [`internal/agent/copilot.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/agent/copilot.go) handles the `assistant.message_delta` type for streaming chunks:

```go
func parseCopilotEvents(
    ctx context.Context,
    r io.Reader,
    onChunk func(string),
    usage *TokenUsage,
    messages *[]string,
    copilotErr *string,
    exitCode *int,
) error {
    scanner := bufio.NewScanner(r)
    // ...
    switch event.Type {
    case "assistant.message_delta":
        if onChunk != nil {
            onChunk(event.Data.DeltaContent)
        }
    case "assistant.message":
        *messages = append(*messages, event.Data.Content)
        usage.Add(TokenUsage{OutputTokens: event.Data.OutputTokens})
    }
    return nil
}

```

Unlike Claude, Copilot explicitly tracks exit codes and accumulates complete messages separately from delta chunks. The `finalizeCopilotResult()` function walks the list of assistant messages in reverse order, attempting to parse each against the JSON schema before falling back to raw text.

## Shared Infrastructure

### Common Retry Classification

The `runWithRetry()` function in [`internal/agent/native_command.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/agent/native_command.go) accepts a `classify` callback that evaluates error types to determine if they represent transient failures warranting retry. This shared logic standardizes resilience across all native agents while allowing agent-specific error detection.

### Resource Cleanup

Both native agents implement `Close()` as a no-op since the underlying CLI processes are ephemeral and managed through context cancellation. No persistent resources require explicit cleanup beyond the process management handled by the `os/exec` package and `shellenv` utilities.

## Summary

- **Minimal Interface**: Native agents implement `Name()`, `Run()`, and `Close()` from the `Agent` interface defined in [`internal/agent/agent.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/agent/agent.go).
- **CLI Wrapping**: Both Claude and Copilot runners launch vendor binaries via `exec.CommandContext` and manage process groups through `shellenv.ConfigureShellCommand`.
- **Real-time Streaming**: Line-delimited JSON parsers (`parseClaudeEvents` and `parseCopilotEvents`) consume CLI output incrementally, forwarding partial text via the `OnChunk` callback.
- **Resilient Execution**: The shared `runWithRetry` wrapper in [`internal/agent/native_command.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/agent/native_command.go) provides automatic retry logic for transient failures up to three attempts.
- **Schema Validation**: Finalize functions convert raw CLI output into structured `Result` objects, validating against JSON schemas when specified.

## Frequently Asked Questions

### How do native agent runners handle process cleanup?

Native agents rely on context cancellation and process group isolation rather than explicit resource management. The `shellenv.ConfigureShellCommand()` helper places CLI processes in dedicated process groups, ensuring that subprocesses terminate when the parent context is cancelled. The `Close()` method is a no-op since no persistent handles require manual cleanup.

### What distinguishes Claude's streaming format from Copilot's?

Claude emits `--output-format stream-json` with event types like `assistant` and `result`, while Copilot uses a JSONL format with `assistant.message_delta` events for streaming chunks and `assistant.message` for complete responses. Despite these structural differences, both parsers deliver identical real-time behavior through the `OnChunk` callback.

### How does the retry mechanism determine which errors are transient?

The `runWithRetry()` function accepts a classifier callback that evaluates error types against agent-specific criteria. While the implementation identifies network interruptions and rate limits as transient, the architecture allows each runner to define its own error classification logic while sharing the retry loop infrastructure and the `claudeMaxRetries` constant (set to 3).

### Can developers customize the CLI arguments passed to native agents?

Yes. Both `claudeAgent` and `copilotAgent` structs maintain an `extraArgs` slice that accumulates user-provided flags. The `buildArgs()` method (and `buildCopilotPrompt()` for Copilot) merges these with default parameters such as `--output-format stream-json` for Claude, allowing injection of vendor-specific flags while preserving required streaming configuration.