Keen Code - a coding agent CLI | System Architecture RFC¶
This RFC was the initial proposed architecture of Keen Code. The finished product may differ from this document.
1. Overview¶
This document outlines the system architecture for Keen Code, a terminal-based coding agent CLI written in Go. The tool provides AI-assisted code editing capabilities with two primary modes: plan (suggestions only) and work (with permission-based edits).
2. High-Level Architecture¶
2.1 Component Overview¶
graph TB
subgraph "User Interface Layer"
CLI[CLI Parser<br/>github.com/spf13/cobra]
REPL[Interactive REPL<br/>github.com/charmbracelet/bubbletea]
CONFIG[Config Manager]
end
subgraph "Core Engine Layer"
ORCH[Orchestrator]
MODE[Mode Controller<br/>Plan/Work]
SESSION[Session Manager]
end
subgraph "Tool System Layer"
TOOLM[Tool Manager]
FILE_READ[File Reader]
FILE_WRITE[File Writer]
SHELL[Shell Executor]
GREP[Search/Grep]
end
subgraph "Git Awareness Layer"
GIT[GitAwareness]
GITIGNORE[.gitignore Parser]
IGNORE_CACHE[Ignore Cache]
end
subgraph "LLM Integration Layer"
GENKIT[Genkit for Go<br/>github.com/firebase/genkit/go]
MODELS[LLM Models]
end
subgraph "External Dependencies"
ANTHROPIC_API[Anthropic API]
OPENAI_API[OpenAI API]
GEMINI_API[Google Gemini API]
FILE_SYSTEM[File System]
SHELL_ENV[Shell Environment]
end
CLI --> ORCH
REPL --> ORCH
CONFIG --> ORCH
ORCH --> MODE
ORCH --> SESSION
ORCH --> TOOLM
MODE --> TOOLM
TOOLM --> FILE_READ
TOOLM --> FILE_WRITE
TOOLM --> SHELL
TOOLM --> GREP
ORCH --> GENKIT
GENKIT --> MODELS
MODELS --> ANTHROPIC_API
MODELS --> OPENAI_API
MODELS --> GEMINI_API
FILE_READ --> FILE_SYSTEM
FILE_WRITE --> FILE_SYSTEM
SHELL --> SHELL_ENV
GREP --> FILE_SYSTEM
GIT --> GITIGNORE
GIT --> IGNORE_CACHE
FILE_READ -.->|respects| GIT
FILE_WRITE -.->|respects| GIT
GREP -.->|respects| GIT
2.2 Data Flow¶
sequenceDiagram
participant User
participant CLI
participant Orchestrator
participant Genkit
participant ToolManager
participant FileSystem
User->>CLI: Enter prompt
CLI->>Orchestrator: ProcessRequest(prompt, mode)
Orchestrator->>Orchestrator: Load system prompt + context
loop Tool Calling Loop
Orchestrator->>Genkit: Send messages
Genkit-->>Orchestrator: Response (text/tools)
alt Response contains tool calls
Orchestrator->>ToolManager: Execute tools
alt Tool is file_read
ToolManager->>FileSystem: Read file(s)
FileSystem-->>ToolManager: File contents
else Tool is file_write
ToolManager->>FileSystem: Write file (Work mode only)
FileSystem-->>ToolManager: Success/Error
else Tool is shell
ToolManager->>FileSystem: Execute command
FileSystem-->>ToolManager: Output
end
ToolManager-->>Orchestrator: Tool results
else Response is final answer
Orchestrator-->>CLI: Final response
end
end
CLI-->>User: Display result
3. Detailed Component Design¶
3.1 Module Structure¶
keen-cli/
├── cmd/
│ └── agent/
│ └── main.go # Entry point
├── internal/
│ ├── cli/
│ │ ├── root.go # Cobra root command
│ │ ├── repl.go # Interactive REPL command
│ │ └── config.go # Config commands
│ ├── config/
│ │ ├── config.go # Config struct and defaults
│ │ └── loader.go # YAML config loading
│ ├── orchestrator/
│ │ ├── orchestrator.go # Main orchestration logic
│ │ ├── context.go # Context/conversation management
│ │ └── mode.go # Plan/Work mode handling
│ ├── llm/
│ │ ├── client.go # Genkit client wrapper
│ │ ├── models.go # Model configuration & factory
│ │ └── message.go # Message types
│ ├── tools/
│ │ ├── manager.go # Tool execution manager
│ │ ├── registry.go # Tool registry
│ │ ├── read_file.go # File reading tool
│ │ ├── write_file.go # File writing tool
│ │ ├── edit_file.go # File editing tool
│ │ ├── shell.go # Shell execution tool
│ │ ├── grep.go # Search tool
│ │ └── list_dir.go # Directory listing tool
│ ├── filesystem/
│ │ ├── guard.go # Path security guard
│ │ ├── gitawareness.go # Git ignore handling - CRITICAL
│ │ └── watcher.go # Optional: file watching
│ └── ui/
│ ├── renderer.go # Output formatting
│ ├── diff.go # Diff display
│ └── confirm.go # User confirmation prompts
├── pkg/
│ └── api/ # Public API (if needed)
├── configs/
│ └── system_prompts/ # Default system prompts
├── go.mod
├── go.sum
└── README.md
3.2 Core Interfaces¶
classDiagram
class LLMClient {
<<interface>>
+Chat(messages []Message, tools []Tool) Response
+Stream(messages []Message, tools []Tool) chan StreamEvent
}
class Tool {
<<interface>>
+Name() string
+Description() string
+Schema() JSONSchema
+Execute(args map[string]any) ToolResult
}
class Orchestrator {
-client LLMClient
-tools []Tool
-mode Mode
-session Session
+Process(input string) error
-buildSystemPrompt() string
}
class Mode {
<<enumeration>>
Plan
Work
}
class FileGuard {
-rootDir string
-allowedPaths []string
+ValidatePath(path string) error
+ResolvePath(path string) string
}
class GitAwareness {
-ignoreMatchers []IgnoreMatcher
-cache map[string]bool
+LoadGitignore(path string) error
+IsIgnored(filePath string) bool
+FilterPaths(paths []string) []string
}
class IgnoreMatcher {
-patterns []string
-basePath string
+Match(path string) bool
}
GitAwareness --> IgnoreMatcher
LLMClient <|.. GenkitClient
Tool <|.. ReadFileTool
Tool <|.. WriteFileTool
Tool <|.. EditFileTool
Tool <|.. ShellTool
Tool <|.. GrepTool
Orchestrator --> LLMClient
Orchestrator --> Tool
Orchestrator --> Mode
ReadFileTool --> FileGuard
WriteFileTool --> FileGuard
3.3 GitAwareness Component¶
Purpose: Prevent wasting tokens and confusing the LLM by filtering out files that should be ignored according to .gitignore rules.
flowchart TD
A[list_dir or grep tool] --> B{GitAwareness}
B --> C[Load .gitignore from root]
C --> D[Load .gitignore from subdirs]
D --> E[Build ignore matchers]
F[File path] --> B
B --> G{Is Ignored?}
G -->|Yes| H[Skip file]
G -->|No| I[Include file]
Example Flow:
User: "explain this codebase"
→ list_dir called on project root
→ GitAwareness loads:
- .gitignore (node_modules/, *.log, .env)
- src/.gitignore (*.test.js)
→ Returns filtered list WITHOUT:
- node_modules/ (saves ~100k+ tokens)
- .env files (security)
- build artifacts
Configuration:
filesystem:
respect_gitignore: true # Default: true
additional_ignores:
- "*.min.js"
- "dist/"
3.4 Tool System Detail¶
The tool system uses a registry pattern for extensibility:
4. Implementation Steps¶
Phase 1: Foundation¶
flowchart TD
A[Initialize Go Module] --> B[Set up project structure]
B --> C[Implement Config System]
C --> D[Implement FileGuard]
D --> E[Implement GitAwareness]
E --> F[Create basic CLI commands]
F --> G[Add logging framework]
Phase 2: LLM Integration¶
flowchart TD
A[Add Genkit for Go Dependency] --> B[Create LLM Client Wrapper]
B --> C[Configure Anthropic Provider]
B --> D[Configure OpenAI Provider]
B --> E[Configure Gemini Provider]
C --> F[Add Streaming Support]
D --> F
E --> F
F --> G[Test LLM Integration]
Phase 3: Tool System¶
flowchart TD
A[Define Tool Interface] --> B[Implement Read File Tool]
A --> C[Implement List Directory Tool]
A --> D[Implement Grep/Search Tool]
A --> E[Implement Shell Tool]
A --> F[Implement Write File Tool]
A --> G[Implement Edit File Tool]
B --> H[Create Tool Manager]
C --> H
D --> H
E --> H
F --> H
G --> H
Tools to implement:
| Tool | Description | Libraries |
|---|---|---|
read_file |
Read file contents | Standard os package |
list_dir |
List directory contents | Standard os package |
grep_search |
Search file contents | github.com/bmatcuk/doublestar + std lib |
shell |
Execute shell commands | os/exec with timeout/sandboxing |
write_file |
Write/overwrite files | Standard os package |
edit_file |
Apply string replacements | Custom implementation |
Phase 4: Orchestrator & Modes¶
flowchart TD
A[Implement Orchestrator] --> B[Implement Plan Mode]
A --> C[Implement Work Mode]
B --> D[Add confirmation prompts]
C --> D
D --> E[Build conversation context]
E --> F[Add system prompts]
Phase 5: Interactive UI¶
flowchart TD
A[Implement REPL] --> B[Add syntax highlighting]
A --> C[Add diff display]
B --> D[Integrate with Orchestrator]
C --> D
D --> E[Add keyboard shortcuts]
E --> F[Polish UX]
Phase 6: Testing & Polish¶
flowchart TD
A[Unit Tests] --> B[Integration Tests]
B --> C[End-to-end Tests]
C --> D[Documentation]
D --> E[Performance Optimization]
E --> F[Release]
5. External Dependencies¶
5.1 Required Libraries¶
| Category | Library | Purpose |
|---|---|---|
| CLI Framework | github.com/spf13/cobra |
Command-line interface |
| Config Management | gopkg.in/yaml.v3 |
Configuration loading |
| TUI Framework | github.com/charmbracelet/bubbletea |
Interactive REPL |
| TUI Components | github.com/charmbracelet/lipgloss |
Styled terminal output |
| User Prompts | github.com/charmbracelet/huh |
Interactive forms and prompts |
| Syntax Highlighting | github.com/alecthomas/chroma |
Code display |
| Diff Display | github.com/sergi/go-diff/diffmatchpatch |
Diff generation |
| LLM Framework | github.com/firebase/genkit/go |
Unified LLM interface |
| Pattern Matching | github.com/bmatcuk/doublestar |
Glob patterns |
| Gitignore Parsing | github.com/go-git/go-git/v5/plumbing/format/gitignore |
Parse .gitignore rules |
| Environment | github.com/joho/godotenv |
.env file support |
5.2 Go Standard Library Usage¶
os,os/exec- File operations and shell executionpath/filepath- Cross-platform path handlingcontext- Request cancellation and timeoutsencoding/json- JSON marshaling/unmarshalinglog/slog- Structured logging (Go 1.21+)sync- Concurrency primitives
6. Security Considerations¶
6.1 File System Security¶
flowchart LR
A[Tool Request] --> B{FileGuard}
B -->|Path validation| C{Within bounds?}
C -->|No| D[Reject with Error]
C -->|Yes| E{GitAwareness}
E -->|Check .gitignore| F{Is Ignored?}
F -->|Yes| G[Skip/Audit Log]
F -->|No| H[Execute Tool]
H --> I[Audit Log]
FileGuard Rules:
- All paths resolved relative to working directory
- Block access to parent directories (../)
- Block access to sensitive paths (~/.ssh, /etc, etc.)
- Whitelist/blacklist configuration support
- All file operations logged
GitAwareness Rules:
- Respect .gitignore by default (prevents token waste on node_modules, build artifacts)
- Prevent accidental exposure of secrets (.env files)
- Cache ignore patterns for performance
- Support nested .gitignore files
- Configurable override if needed
6.2 Shell Execution Security¶
- Shell commands run with current user permissions
- Optional command whitelist/blacklist
- Timeout on long-running commands
- Clear user confirmation for destructive commands
6.3 API Key Security¶
- Support environment variables for API keys
- Support
.envfiles (gitignored by default) - Config file permissions check (0600)
7. Configuration¶
7.1 Configuration Sources (Priority Order)¶
- Command-line flags (highest)
- Environment variables
.keen/config.yamlin project~/.config/keen/config.yaml(global)- Defaults (lowest)
7.2 Configuration Schema¶
# config.yaml
llm:
provider: anthropic # anthropic | openai | gemini
model: claude-3-sonnet-20240229
api_key: ${ANTHROPIC_API_KEY}
base_url: "" # For custom endpoints/proxy
temperature: 0.7
max_tokens: 4096
# Provider-specific settings
anthropic:
thinking_budget: 0
openai:
organization: ""
gemini:
api_key: ${GEMINI_API_KEY}
tools:
enabled:
- read_file
- write_file
- edit_file
- list_dir
- grep_search
- shell
shell:
allowed_commands: [] # Empty = all allowed
blocked_commands: ["rm -rf /", "sudo"]
timeout: 30s
file:
max_read_size: 1MB
allowed_extensions: [] # Empty = all allowed
blocked_paths:
- ~/.ssh
- /etc
ui:
theme: auto # auto | light | dark
syntax_highlighting: true
diff_context_lines: 3
confirm_destructive: true
behavior:
default_mode: plan # plan | work
auto_apply_safe: false # Auto-apply non-destructive changes
max_iterations: 10 # Max tool call loops
logging:
level: info # debug | info | warn | error
file: "" # Empty = stderr only
8. User Interface Design¶
8.1 Command Structure¶
# Start interactive REPL
keen
# Start REPL with custom provider settings
keen --provider anthropic --model claude-3-opus
# Version
keen --version
# Session config
keen --provider anthropic --provider-api-key $ANTHROPIC_API_KEY --model claude-haiku-4-5-20251001
8.2 Interactive REPL Commands¶
| Command | Description |
|---|---|
/plan |
Switch to plan mode |
/work |
Switch to work mode |
/add <file> |
Add file to context |
/drop <file> |
Remove file from context |
/clear |
Clear conversation |
/models |
List available models |
/config |
Show current config |
/exit or Ctrl+D |
Exit REPL |
8.3 REPL Interaction Flow¶
$ keen
🤖 Keen v0.1.0 (plan mode)
Working directory: /home/user/myproject
Type /help for commands, /exit to quit
> create a fibonacci function
I'll help you create a fibonacci function. Let me first check the current project structure.
[Tool: list_dir] → main.go, utils/
I see this is a Go project. I'll create a fibonacci function in a new mathutils.go file:
═══════════════════════════════════════════════════════
PLANNED CHANGES (Plan Mode - no files modified)
═══════════════════════════════════════════════════════
Create: mathutils.go
───────────────────────────────────────────────────────
+ package utils
+
+ func Fibonacci(n int) int {
+ if n <= 1 {
+ return n
+ }
+ return Fibonacci(n-1) + Fibonacci(n-2)
+ }
Would you like me to apply these changes? Switch to work mode with /work
> /work
Switched to work mode
> apply the fibonacci changes
I'll create the mathutils.go file with the fibonacci function.
Create mathutils.go? [Y/n/d(details)] y
✓ Created mathutils.go
═══════════════════════════════════════════════════════
DONE
═══════════════════════════════════════════════════════
Created mathutils.go with a recursive fibonacci function.
The function has exponential time complexity O(2^n).
Consider using memoization for better performance with large inputs.
>
9. Error Handling & Recovery¶
9.1 Error Categories¶
| Category | Examples | Handling |
|---|---|---|
| User Input | Invalid commands, bad paths | Clear error message, suggest fixes |
| LLM Errors | Rate limits, API errors | Retry with backoff, graceful degradation |
| Tool Errors | File not found, permission denied | Return to LLM for correction |
| System Errors | Disk full, network errors | Save state, exit cleanly |
9.2 Retry Logic¶
flowchart TD
A[LLM Request] --> B{Success?}
B -->|Yes| C[Continue]
B -->|No| D{Retryable?}
D -->|Yes| E[Exponential Backoff]
E --> A
D -->|No| F[Return Error]
F --> G[Suggest Alternative]
10. Testing Strategy¶
10.1 Test Pyramid¶
/\
/ \ E2E Tests (Full CLI workflows)
/____\
/ \ Integration Tests (Tool + LLM)
/________\
/ \ Unit Tests (Individual components)
/____________\
10.2 Testing Approach¶
| Layer | Tools | Focus |
|---|---|---|
| Unit | testing, testify |
Individual tools, parsers, utilities |
| Integration | httptest |
LLM providers with mock servers |
| E2E | exec, golden files |
Full CLI commands |
11. Future Enhancements¶
11.1 Planned Features¶
- Multi-file editing - Atomic operations across multiple files
- Advanced Git integration - Auto-commit, branch creation, diff viewing (Basic .gitignore support is Phase 1)
- Code indexing - FAISS/vector search for large codebases
- Plugin system - Custom tool registration
- Web interface - Optional browser-based UI
- Collaboration - Session sharing, comments
11.2 Architecture Extensibility¶
graph LR
subgraph "Current"
A[CLI] --> B[Orchestrator]
B --> C[Tools]
B --> D[Genkit]
end
subgraph "Future Extensions"
E[Web UI] --> B
F[Git Hook] --> B
G[MCP Server] --> B
C --> H[Custom Plugins]
D --> I[Local Models]
end
12. Appendix¶
12.1 System Prompt Template¶
You are a helpful coding assistant. You have access to tools for reading and
modifying files in the user's project.
Current mode: {{MODE}}
Working directory: {{WORKING_DIR}}
{{MODE_INSTRUCTIONS}}
Available tools:
{{TOOL_DESCRIPTIONS}}
When suggesting changes:
1. Explain what you plan to do
2. Use tools to gather information
3. {{APPLY_INSTRUCTIONS}}
4. Explain what was done
12.2 Tool Schema Example¶
{
"name": "read_file",
"description": "Read the contents of a file",
"input_schema": {
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "Relative path to the file"
}
},
"required": ["path"]
}
}
13. Summary¶
This architecture provides a solid foundation for a coding agent CLI with:
- ✅ Clean separation of concerns (UI, Orchestration, Tools, LLM)
- ✅ GitAwareness component (Phase 1) - Respects
.gitignoreto avoid wasting tokens - ✅ Unified LLM integration via Genkit for Go for extensibility
- ✅ Single dependency for multiple LLM providers
- ✅ Extensible tool system
- ✅ Secure file system access
- ✅ Two-mode operation (Plan/Work)
- ✅ Idiomatic Go patterns
- ✅ Minimal external dependencies
- ✅ Testable design