How no-mistakes Integrates with Claude, Codex, and OpenCode Agents
no-mistakes treats every AI driver as a first-class plugin that satisfies a single Go interface, allowing seamless swapping between Claude, Codex, and OpenCode without changing pipeline logic.
The no-mistakes repository provides a unified abstraction layer for AI coding agents. At its core, the project defines a common Agent interface in internal/agent/agent.go that standardizes how the application communicates with Claude, Codex, and OpenCode. This design enables the CLI and pipeline steps to remain agnostic to the underlying model implementation.
The Core Agent Interface Contract
The heart of the integration lives in internal/agent/agent.go, which declares the Agent interface that every AI driver must implement:
type Agent interface {
Name() string
Run(context.Context, RunOpts) (*Result, error)
Close() error
}
The RunOpts struct carries the execution context, including the prompt string, working directory, an optional JSON schema for structured output, and a streaming callback (OnChunk). The Result struct returns the raw text output, an optional structured JSON blob, and token usage accounting. This contract ensures that any agent—whether Claude, Codex, or OpenCode—can be invoked identically from the pipeline.
Claude Integration via CLI Wrapping
The Claude implementation in internal/agent/claude.go acts as a wrapper around the Claude CLI binary. When Run is invoked, the agent spawns the Claude executable (a.bin) with arguments constructed by buildArgs. It uses shellenv.ConfigureShellCommand to ensure the subprocess is properly reaped on exit.
The implementation reads a stream-JSON event feed via parseClaudeEvents, which provides real-time token usage metrics and a final claudeResult. When a JSON schema is supplied, the agent post-processes the output through finalizeClaudeResult to extract structured data from the model's response.
Codex Integration with Schema Files
The Codex driver in internal/agent/codex.go follows a similar CLI-wrapping pattern but adapts to Codex's specific output format. The agent calls the Codex CLI (a.bin exec …) and writes a temporary output-schema file when structured output is requested.
It parses Codex's JSON-L event stream using parseCodexEvents to capture the final agent_message and token usage. Unlike Claude, Codex only returns raw text, so the driver uses finalizeTextResult to package the response. The agent handles the temporary file cleanup automatically after the execution completes.
OpenCode Integration via HTTP Server
The OpenCode implementation in internal/agent/opencode.go takes a different approach by managing a persistent HTTP server. The agent starts opencode serve via ensureServer and maintains the process for the duration of the session.
It opens an SSE event stream through connectEventStream and posts prompts via sendMessage. The parseOpencodeSSE function handles incremental text chunks as they arrive. After the message finishes, the agent prefers the structured payload (Info.Structured) and falls back to JSON extraction from raw text using finalizeTextResult if needed.
Factory Pattern and CLI Wiring
All three agents are wired into the system through a factory function in internal/agent/agent.go:
func NewWithOptions(name types.AgentName, bin string, args []string,
opts Options) (Agent, error) {
switch name {
case types.AgentClaude:
return &claudeAgent{bin: bin, extraArgs: args}, nil
case types.AgentCodex:
return &codexAgent{bin: bin, extraArgs: args}, nil
case types.AgentOpenCode:
return &opencodeAgent{bin: bin, extraArgs: args}, nil
}
return nil, fmt.Errorf("unknown agent %q", name)
}
The CLI wizard in internal/cli/wizard.go instantiates the concrete agent by calling agent.NewWithOptions with configuration values from .no-mistakes.yaml or command-line flags. Pipeline steps receive the Agent interface and invoke it uniformly. For example, in internal/pipeline/steps/review.go:
res, err := a.Agent.Run(ctx, RunOpts{
Prompt: prompt,
CWD: workDir,
JSONSchema: schema,
OnChunk: onChunk,
})
Retry Logic and Error Handling
The base agent implementation provides a generic retry wrapper called runWithRetry that all concrete agents invoke. This function classifies transient errors using classifyTransient and agent-specific logic like claudeRetryClassifier, capping attempts at claudeMaxRetries. This ensures resilience against temporary network failures or rate limits without duplicating logic across agent implementations.
Practical Usage Examples
Directly Invoking an Agent from Go
You can instantiate any agent programmatically using the factory:
import (
"context"
"github.com/kunchenguid/no-mistakes/internal/agent"
"github.com/kunchenguid/no-mistakes/internal/types"
)
func invokeClaude() (*agent.Result, error) {
a, err := agent.NewWithOptions(types.AgentClaude, "claude", nil, agent.Options{})
if err != nil {
return nil, err
}
defer a.Close()
return a.Run(context.Background(), agent.RunOpts{
Prompt: "Fix the bug in the supplied Go file.",
CWD: "/tmp/repo",
JSONSchema: []byte(`{"type":"object","properties":{"fixed":{"type":"boolean"}}}`),
OnChunk: func(txt string) { fmt.Print(txt) },
})
}
Switching Agents via CLI
Change the active agent without modifying code:
# Use Claude (default)
no-mistakes run --prompt "Suggest a better test name."
# Switch to Codex
no-mistakes run --agent codex --prompt "Rewrite the function in Rust."
# Use OpenCode
no-mistakes run --agent opencode --prompt "Explain this code."
Summary
- Unified Interface: The
Agentinterface ininternal/agent/agent.godefinesName(),Run(), andClose()methods that standardize interaction across all AI drivers. - Claude: Wraps the CLI binary with JSON event streaming and structured output support via
finalizeClaudeResult. - Codex: Wraps the CLI with temporary schema file handling but returns only raw text through
finalizeTextResult. - OpenCode: Manages a persistent HTTP server with SSE streaming and prefers structured payloads from
Info.Structured. - Zero Pipeline Changes: The factory pattern in
NewWithOptionsand the wizard ininternal/cli/wizard.goenable runtime agent selection without touching pipeline step code.
Frequently Asked Questions
How do I switch between Claude and Codex in no-mistakes?
Pass the --agent flag with the driver name, or set the agent field in your .no-mistakes.yaml configuration file. The CLI wizard in internal/cli/wizard.go parses this value and passes it to agent.NewWithOptions, which instantiates the correct concrete implementation without requiring changes to pipeline logic.
Can I add a custom AI agent to no-mistakes?
Yes. Implement the Agent interface from internal/agent/agent.go in a new file (e.g., custom.go), then add a case in the NewWithOptions factory function to return your implementation when the appropriate types.AgentName is requested. The rest of the system will automatically recognize the new agent once referenced in configuration.
Why does Codex not return structured output like Claude?
According to the source code in internal/agent/codex.go, Codex only returns raw text responses. While the agent still accepts a JSON schema and writes a temporary schema file for the CLI to consume, the finalizeTextResult handler indicates that Codex does not populate the structured JSON field in the Result object, unlike Claude which uses finalizeClaudeResult for JSON extraction.
How does no-mistakes handle transient failures with OpenCode?
All agents, including OpenCode, leverage the runWithRetry wrapper defined in internal/agent/agent.go. This function implements exponential backoff with agent-specific classifiers like claudeRetryClassifier and caps retry attempts at claudeMaxRetries. For OpenCode specifically, this handles temporary HTTP connection drops or SSE stream interruptions automatically.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →