Design Decisions¶
This document explains the key architectural decisions made during clinvoker development and the rationale behind them. Understanding these decisions helps developers contribute effectively and users understand the system's behavior.
Why Go Was Chosen¶
The Decision¶
clinvoker is implemented in Go (Golang).
Rationale¶
-
Single Binary Deployment: Go compiles to a single static binary with no runtime dependencies, making distribution trivial.
-
Excellent Concurrency: Go's goroutines and channels provide lightweight concurrency primitives perfect for handling multiple backends and requests.
-
Standard Library: Rich standard library includes HTTP servers, JSON handling, and subprocess management without external dependencies.
-
Cross-Platform: Native support for Windows, macOS, and Linux with minimal platform-specific code.
-
Fast Compilation: Quick build times improve developer productivity.
Alternatives Considered¶
| Language | Pros | Cons |
|---|---|---|
| Python | Ecosystem, AI libraries | Deployment complexity, GIL limitations |
| Rust | Performance, Safety | Steeper learning curve, longer compile times |
| Node.js | JavaScript familiarity | Runtime dependency, callback complexity |
| Java | Mature ecosystem | JVM requirement, verbose |
Why Cobra for CLI¶
The Decision¶
Cobra is used as the CLI framework.
Rationale¶
-
Industry Standard: Used by Kubernetes, Hugo, and many other major Go projects.
-
Rich Features: Built-in help generation, shell completion, and flag parsing.
-
Command Hierarchy: Natural support for subcommands with persistent and local flags.
-
Documentation: Auto-generated documentation from code.
-
Validation: Built-in flag validation and error handling.
Implementation Pattern¶
var rootCmd = &cobra.Command{
Use: "clinvk",
Short: "Unified AI CLI wrapper",
Long: `A unified interface for Claude Code, Codex CLI, and Gemini CLI.`,
RunE: runRoot,
}
func init() {
rootCmd.PersistentFlags().String("backend", "", "AI backend to use")
rootCmd.PersistentFlags().String("model", "", "Model to use")
// ...
}
Why Chi for HTTP Router¶
The Decision¶
Chi is used as the HTTP router and middleware framework.
Rationale¶
-
Lightweight: Minimal overhead, idiomatic Go design.
-
Middleware Chain: Elegant middleware composition with
Use()pattern. -
Context-Aware: Built on
context.Contextfor request-scoped values. -
URL Parameters: Clean URL parameter extraction.
-
Compatibility: Works seamlessly with standard
http.Handler.
Middleware Stack¶
router := chi.NewRouter()
router.Use(middleware.RequestID)
router.Use(middleware.RealIP)
router.Use(middleware.Recoverer)
router.Use(middleware.Logger)
router.Use(middleware.Timeout(60 * time.Second))
Why Huma for OpenAPI¶
The Decision¶
Huma is used for OpenAPI generation and request/response validation.
Rationale¶
-
Code-First: Generate OpenAPI spec from Go code, not vice versa.
-
Type Safety: Request/response types validated at compile time.
-
Automatic Documentation: Interactive docs generated from code.
-
Validation: Automatic request validation based on struct tags.
-
Multiple Adapters: Works with Chi, Gin, and other routers.
Example Usage¶
huma.Register(api, huma.Operation{
OperationID: "create-chat-completion",
Method: http.MethodPost,
Path: "/openai/v1/chat/completions",
}, func(ctx context.Context, input *ChatRequest) (*ChatResponse, error) {
// Handler implementation
})
Why Subprocess Execution Instead of SDK¶
The Decision¶
clinvoker executes AI CLI tools as subprocesses rather than using their SDKs directly.
Rationale¶
-
Zero Configuration: CLI tools handle authentication, API keys, and configuration automatically.
-
Always Up-to-Date: No need to update clinvoker when SDK APIs change.
-
Feature Parity: CLI tools often have features not available in SDKs.
-
Session Management: Leverage built-in session handling of CLI tools.
-
Simplicity: One abstraction layer instead of multiple SDK integrations.
Trade-offs¶
| Aspect | Subprocess Approach | SDK Approach |
|---|---|---|
| Startup Time | Slightly slower | Faster |
| Dependencies | Fewer | More libraries |
| Maintenance | Lower | Higher |
| Feature Access | Full CLI features | SDK-limited |
| Authentication | CLI handles it | Code required |
SDK Compatibility Approach¶
The Decision¶
Provide OpenAI and Anthropic-compatible API endpoints alongside native REST API.
Rationale¶
-
Ecosystem Compatibility: Existing tools using OpenAI SDK work without modification.
-
Migration Path: Easy transition from cloud APIs to local CLI tools.
-
Framework Support: LangChain, LangGraph, and similar frameworks work out of the box.
-
Familiar Interface: Developers already know these APIs.
Implementation Strategy¶
flowchart TB
subgraph Input["Client Request"]
OPENAI[OpenAI Format]
ANTH[Anthropic Format]
NATIVE[Native Format]
end
subgraph Transform["Transformation Layer"]
MAP[Unified Options Mapper]
end
subgraph Internal["Internal Processing"]
EXEC[Executor]
BACKEND[Backend]
end
OPENAI --> MAP
ANTH --> MAP
NATIVE --> MAP
MAP --> EXEC
EXEC --> BACKEND
Session Persistence Trade-offs¶
The Decision¶
Sessions are persisted to local filesystem with JSON format.
Rationale¶
-
Simplicity: No external database required.
-
Portability: Easy to backup, migrate, and inspect.
-
Human-Readable: JSON format allows manual inspection and debugging.
-
Version Control: Sessions can be version controlled if desired.
File-based vs Database Storage¶
| Aspect | File-based | Database |
|---|---|---|
| Setup | None required | Installation required |
| Complexity | Low | Higher |
| Querying | Limited | Rich |
| Concurrency | File locking | ACID transactions |
| Scalability | Single machine | Distributed |
| Backup | File copy | Database backup |
Why Not SQLite?¶
SQLite was considered but rejected because: - JSON files are easier to inspect and debug - No schema migration complexity - Simpler backup and restore - Cross-process access is straightforward with file locking
Backend Abstraction Design Choices¶
The Decision¶
Use a common Backend interface with unified options mapping.
Rationale¶
-
Polymorphism: Treat all backends uniformly in core code.
-
Extensibility: Easy to add new backends without modifying core.
-
Testability: Mock backends for testing.
-
Consistency: Same API regardless of backend.
Interface Design¶
type Backend interface {
Name() string
IsAvailable() bool
BuildCommand(prompt string, opts *Options) *exec.Cmd
ResumeCommand(sessionID, prompt string, opts *Options) *exec.Cmd
ParseOutput(rawOutput string) string
ParseJSONResponse(rawOutput string) (*UnifiedResponse, error)
}
Concurrency Model Selection¶
The Decision¶
Use sync.RWMutex for in-process concurrency and file locking for cross-process synchronization.
Rationale¶
-
Read-Heavy Workload: Most operations are reads (listing, getting sessions).
-
Go Idiomatic: Standard Go pattern for concurrent access.
-
Cross-Process Safety: File locks enable CLI and server coexistence.
-
Simplicity: Easier to reason about than channel-based approaches.
Concurrency Patterns¶
// Read operation
func (s *Store) Get(id string) (*Session, error) {
s.mu.RLock()
defer s.mu.RUnlock()
return s.getLocked(id)
}
// Write operation
func (s *Store) Save(sess *Session) error {
s.mu.Lock()
defer s.mu.Unlock()
return s.saveLocked(sess)
}
Configuration Cascade Design¶
The Decision¶
Configuration follows a cascade: CLI flags -> Environment -> Config file -> Defaults.
Rationale¶
-
Predictable Override: Higher priority sources always win.
-
Environment Friendly: Works well in containers and CI/CD.
-
User-Controllable: Easy to override without changing files.
-
Secure Defaults: Safe configuration when nothing specified.
Resolution Example¶
# Config file: ~/.clinvk/config.yaml
default_backend: claude
backends:
codex:
model: o3
# Environment
CLINVK_BACKEND=codex
CLINVK_CODEX_MODEL=o3-mini
# CLI
clinvk --backend codex "prompt"
# Result: backend=codex (CLI), model=o3-mini (env)
HTTP Server Design¶
The Decision¶
Single binary serves all endpoints with graceful shutdown.
Key Features¶
-
Standard HTTP/1.1: Maximum compatibility.
-
SSE for Streaming: Server-Sent Events for real-time output.
-
CORS Configurable: For browser-based clients.
-
Health Endpoint:
/healthfor load balancers.
Why Not gRPC?¶
- HTTP is universally supported
- Browser compatibility important
- Simpler debugging with curl
- Most AI SDKs use HTTP/REST
Error Handling Philosophy¶
The Decision¶
Propagate errors with context, fail gracefully.
Principles¶
-
Preserve CLI Exit Codes: Backend errors propagated accurately.
-
Structured Errors: JSON format with error details.
-
Graceful Degradation: Partial results in parallel mode.
-
Detailed Logging: Debug information when needed.
Error Response Format¶
{
"error": {
"code": "backend_error",
"message": "Claude CLI exited with code 1",
"backend": "claude",
"details": "rate limit exceeded"
}
}
Summary Table¶
| Decision | Choice | Key Reason |
|---|---|---|
| Language | Go | Single binary, excellent concurrency |
| CLI Framework | Cobra | Industry standard, rich features |
| HTTP Router | Chi | Lightweight, idiomatic Go |
| OpenAPI | Huma | Code-first, type-safe |
| Execution | Subprocess | Zero configuration, always up-to-date |
| API Format | Multiple | Framework compatibility |
| Sessions | File-based JSON | Simplicity, portability |
| Concurrency | RWMutex + FileLock | Read-heavy, cross-process safe |
| Config | Cascade | Predictable, environment-friendly |
| Server | HTTP/SSE | Universal compatibility |
Future Considerations¶
MCP Server Support¶
MCP support is implemented via clinvk mcp, with both stdio and http transports.
Current capabilities include:
- Tool exposure for prompt, parallel, chain, compare, and session operations
- Transport selection for local and server deployment scenarios
- Optional health endpoint in HTTP transport mode
Future MCP-focused improvements:
- Additional authentication patterns for remote deployments
- Broader MCP ecosystem interoperability testing
- More granular policy controls for tool exposure
Additional Backends¶
The backend abstraction allows adding new AI CLIs as they become available. Requirements for new backends:
- CLI supports non-interactive mode
- Structured output (JSON preferred)
- Session management (optional but preferred)
Related Documentation¶
- Architecture Overview - System architecture
- Backend System - Backend abstraction details
- Session System - Session persistence design