How Claude, Codex, and OpenCode Integrate with no-mistakes via the Native Command Protocol
Claude, Codex, and OpenCode integrate with no-mistakes as external command-line binaries that communicate via a line-delimited JSON protocol over stdin/stdout, enabling the daemon to request operations like list-findings through lightweight structured messages.
The no-mistakes project (kunchenguid/no-mistakes) implements a native command protocol that treats LLM agents as standalone executables rather than linked libraries. By spawning Claude Code, Codex CLI, and OpenCode as subprocesses and exchanging JSON messages over their standard streams, the system achieves loose coupling while maintaining synchronous request-response semantics. This design allows new agents to be added without modifying the daemon's core logic, provided they implement the simple contract defined in internal/agent/native_command.go.
Native Command Protocol Specification
The protocol centers on two structs defined in internal/agent/native_command.go: CommandRequest and CommandResponse. These types enforce a consistent envelope format across all agent integrations.
Request and Response Types
type CommandRequest struct {
Command string `json:"command"` // e.g. "list-findings"
Args []string `json:"args,omitempty"`
Input []byte `json:"input,omitempty"` // optional JSON payload
}
type CommandResponse struct {
Status string `json:"status"` // "ok" or "error"
Output []byte `json:"output,omitempty"`
Message string `json:"message,omitempty"` // error text if Status=="error"
}
Communication occurs through line-delimited JSON: the daemon writes a single JSON-encoded CommandRequest to the agent's stdin and expects a single JSON-encoded CommandResponse on stdout. This design eliminates the need for socket management or HTTP servers while remaining extensible.
Agent Spawning and Lifecycle Management
When the daemon needs to invoke an agent, it uses internal/daemon/manager.go to orchestrate the process through agent.RunOnce. The implementation creates a process group to ensure proper cleanup of child processes and uses context cancellation for timeout handling.
Process Group Setup
The spawning logic in internal/agent/server.go configures the command to run in a new process group. This allows no-mistakes to reap grandchildren processes reliably, preventing zombie processes when agents spawn their own subprocesses.
// internal/daemon/manager.go – start an agent run
ctx, cancel := context.WithCancel(ctx)
defer cancel()
agentPath := cfg.AgentPathOverride[types.AgentClaude] // from config.yaml
cmd := exec.CommandContext(ctx, agentPath, "serve")
shellenv.ConfigureShellCommand(cmd) // creates a process group
cmd.Env = append(os.Environ(), "NM_AGENT=claude") // tell the binary which agent it is
out, err := cmd.CombinedOutput()
if err != nil {
// handle failure to start the agent
}
Binary Discovery and Configuration
Agents are located using the agent_path_override configuration key, which maps agent types to executable paths. If no override is specified, the system falls back to built-in "fake-agent" binaries for testing. The environment setup in internal/agent/env.go injects identifiers like NM_AGENT=claude so the binary knows which role to assume.
Claude, Codex, and OpenCode Integration Points
Each supported agent implements a Run method that satisfies the protocol contract. While the daemon sees a uniform interface, each agent file handles command-specific logic and caching.
Claude Code Implementation
In internal/agent/claude.go, the ClaudeAgent struct implements the handler:
func (a *ClaudeAgent) Run(ctx context.Context, req *CommandRequest) (*CommandResponse, error) {
switch req.Command {
case "list-findings":
return &CommandResponse{
Status: "ok",
Output: marshalFindings(claudeCacheFindings()),
}, nil
// other commands …
}
return &CommandResponse{Status: "error", Message: "unknown command"}, nil
}
Codex CLI and OpenCode
The pattern repeats in internal/agent/codex.go and internal/agent/opencode.go. Each maintains its own cache format—accessed via internal/intent/reader_* files—but exposes identical commands (list-findings, apply-fix, summarize-intent) to the daemon.
Practical Communication Flow
When requesting findings, the daemon builds a CommandRequest, marshals it to JSON, and writes it to the agent's stdin. It then reads the response line and unmarshals the result.
Sending Commands from the Daemon
// Build a request to list findings
req := agent.CommandRequest{
Command: "list-findings",
}
enc, _ := json.Marshal(req)
// Write request line
fmt.Fprintln(agentStdin, string(enc))
// Read response line
line, _ := bufio.NewReader(agentStdout).ReadString('\n')
var resp agent.CommandResponse
json.Unmarshal([]byte(line), &resp)
if resp.Status != "ok" {
log.Fatalf("agent error: %s", resp.Message)
}
var findings []types.Finding
json.Unmarshal(resp.Output, &findings)
This synchronous exchange happens for every command, keeping the interaction model simple and stateless between requests.
Summary
- Claude, Codex, and OpenCode integrate with
no-mistakesas external binaries that communicate via line-delimited JSON over stdin/stdout. - The protocol is defined in
internal/agent/native_command.gousingCommandRequestandCommandResponsestructs, requiring no custom RPC frameworks. - Each agent implements the
Run(ctx context.Context, req *CommandRequest) (*CommandResponse, error)signature in dedicated files:claude.go,codex.go, andopencode.go. - Process lifecycle is managed through
internal/daemon/manager.goandinternal/agent/server.go, creating process groups for clean shutdown and usingagent_path_overridefor binary discovery. - New commands can be added without recompiling the daemon—only the agent binary needs to understand new verbs like
summarize-intentorapply-fix.
Frequently Asked Questions
What is the no-mistakes native command protocol?
The native command protocol is a lightweight communication standard used by no-mistakes to exchange structured messages with LLM agents. It uses line-delimited JSON over stdin/stdout, with CommandRequest and CommandResponse structs defined in internal/agent/native_command.go. This approach treats agents as standard command-line programs rather than linked libraries or network services.
How does no-mistakes locate the Claude or Codex binary?
The system checks the agent_path_override configuration key in config.yaml to find the absolute path to the agent executable. If no override exists for a specific agent type, it falls back to built-in "fake-agent" binaries. The internal/agent/env.go file handles setting environment variables like NM_AGENT to identify which agent role the binary should assume.
Can I add custom commands to the native protocol?
Yes. Because the protocol uses a generic Command string field, you can extend agents to recognize new verbs without modifying the daemon's source code. Simply update your agent implementation in internal/agent/claude.go (or the respective agent file) to handle the new command in the Run method's switch statement. The daemon will pass through any command string sent via agent.RunOnce.
Why does no-mistakes use stdin/stdout instead of HTTP or gRPC?
The stdin/stdout approach eliminates network port management and firewall concerns while enforcing a clean process boundary. The internal/agent/server.go implementation creates process groups that ensure proper cleanup of child processes, making it easier to manage resource-intensive LLM agents that might spawn additional subprocesses. This design also allows agents to be written in any language capable of reading JSON from standard input.
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 →