From ad1d95950e76b2fa82f211d5fc023e780f6c5012 Mon Sep 17 00:00:00 2001 From: Farouk Adeleke Date: Sat, 24 Jan 2026 02:48:40 -0800 Subject: [PATCH] Fix config override bug by recreating LMs after load_state --- README.md | 80 ++++++++++++++++++++++++++++++++++------------------- nanocode.py | 2 ++ 2 files changed, 53 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index d169692..0d5986d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # nanocode -Minimal Claude Code alternative using DSPy RLM! Single Python file, ~390 lines. +Minimal Claude Code alternative using DSPy RLM! Single Python file, ~305 lines. Built using Claude Code, then used to build itself. @@ -41,15 +41,14 @@ from modaic import AutoProgram agent = AutoProgram.from_precompiled( "farouk1/nanocode", config={ - "lm": "openrouter/anthropic/claude-3.5-sonnet", - "max_iters": 20 + "lm": "openrouter/openai/gpt-5.2-codex", + "max_iters": 50 } ) # Run a coding task result = agent(task="What Python files are in this directory?") print(result.answer) -print(result.affected_files) ``` ### Option 2: Run Locally (Interactive CLI) @@ -75,13 +74,14 @@ When using as a Modaic AutoProgram, you can configure these options: | Parameter | Type | Default | Description | |-----------|------|---------|-------------| -| `lm` | str | `openrouter/anthropic/claude-3.5-sonnet` | Primary language model | -| `sub_lm` | str | `openrouter/openai/gpt-4.1` | Sub-LM for reasoning steps | -| `max_iters` | int | `20` | Maximum agent iterations | +| `lm` | str | `openrouter/openai/gpt-5.2-codex` | Primary language model | +| `sub_lm` | str | `openrouter/openai/gpt-5-mini` | Sub-LM for reasoning steps | +| `max_iters` | int | `50` | Maximum agent iterations | | `api_base` | str | `https://openrouter.ai/api/v1` | API base URL | -| `max_tokens` | int | `16000` | Maximum tokens per request | +| `max_tokens` | int | `50000` | Maximum tokens per request | | `max_output_chars` | int | `100000` | Maximum output character limit | | `verbose` | bool | `False` | Enable verbose logging | +| `track_usage` | bool | `True` | Track token usage | Example with custom configuration: @@ -91,11 +91,12 @@ from modaic import AutoProgram agent = AutoProgram.from_precompiled( "farouk1/nanocode", config={ - "lm": "openrouter/openai/gpt-4", - "sub_lm": "openrouter/openai/gpt-3.5-turbo", + "lm": "openrouter/anthropic/claude-sonnet-4", + "sub_lm": "openrouter/openai/gpt-4.1-mini", "max_iters": 30, "max_tokens": 8000, - "verbose": True + "verbose": True, + "track_usage": False } ) ``` @@ -115,14 +116,14 @@ agent = AutoProgram.from_precompiled( The agent has access to the following tools: -| Tool | Function | Description | -|------|----------|-------------| -| `readfile` | `read_file(path, offset, limit)` | Read file contents with line numbers | -| `writefile` | `write_file(path, content)` | Write content to a file | -| `editfile` | `edit_file(path, old, new, replace_all)` | Replace text in a file (old must be unique unless `replace_all=True`) | -| `globfiles` | `glob_files(pattern, path)` | Find files matching a glob pattern, sorted by modification time | -| `grepfiles` | `grep_files(pattern, path)` | Search files for a regex pattern | -| `runbash` | `run_bash(cmd)` | Run a shell command and return output | +| Tool | Description | +|------|-------------| +| `read_file(path, offset, limit)` | Read file contents with line numbers | +| `write_file(path, content)` | Write content to a file | +| `edit_file(path, old, new, replace_all)` | Replace text in a file (old must be unique unless `replace_all=True`) | +| `glob_files(pattern, path)` | Find files matching a glob pattern, sorted by modification time | +| `grep_files(pattern, path)` | Search files for a regex pattern | +| `run_bash(cmd)` | Run a shell command and return output | --- @@ -161,7 +162,7 @@ print(result.answer) # Make edits result = agent(task="Add a comment at the top of README.md") -print(result.affected_files) # ['README.md'] +print(result.answer) ``` --- @@ -183,7 +184,14 @@ nanocode.py │ └── run_bash() - Execute commands ├── DSPy Components │ ├── CodingAssistant (Signature) -│ └── RLMCodingProgram (PrecompiledProgram) +│ ├── RLMCodingProgram (PrecompiledProgram) +│ │ ├── forward() - Run agent on task +│ │ ├── get_tools() - Get available tools +│ │ ├── set_tool() - Add/replace a tool +│ │ ├── remove_tool() - Remove a tool +│ │ ├── reload_lms() - Recreate LMs from config +│ │ └── load_state() - Load state with LM fix +│ └── RLMReasoningCallback └── Modaic Integration └── RLMCodingConfig (PrecompiledConfig) ``` @@ -195,13 +203,14 @@ Configuration class extending `PrecompiledConfig` for experiment-specific parame ```python class RLMCodingConfig(PrecompiledConfig): - max_iters: int = 20 - lm: str = "openrouter/anthropic/claude-3.5-sonnet" - sub_lm: str = "openrouter/openai/gpt-4.1" + max_iters: int = 50 + lm: str = "openrouter/openai/gpt-5.2-codex" + sub_lm: str = "openrouter/openai/gpt-5-mini" api_base: str = "https://openrouter.ai/api/v1" - max_tokens: int = 16000 + max_tokens: int = 50000 max_output_chars: int = 100000 verbose: bool = False + track_usage: bool = True ``` #### `RLMCodingProgram` @@ -212,8 +221,20 @@ class RLMCodingProgram(PrecompiledProgram): config: RLMCodingConfig def forward(self, task: str) -> dspy.Prediction: - # Returns prediction with .answer and .affected_files + # Returns prediction with .answer return self.agent(task=task) + + def get_tools(self) -> dict: + # Returns dict of available tools + + def set_tool(self, name: str, tool: callable): + # Add or replace a tool + + def remove_tool(self, name: str): + # Remove a tool by name + + def reload_lms(self): + # Recreate LM objects from current config ``` #### `CodingAssistant` @@ -221,9 +242,10 @@ DSPy Signature defining the agent's input/output schema. ```python class CodingAssistant(dspy.Signature): - task: str = dspy.InputField() - answer: str = dspy.OutputField() - affected_files: list[str] = dspy.OutputField() + """You are a concise coding assistant with access to sub agents.""" + + task: str = dspy.InputField(desc="The user's coding task or question") + answer: str = dspy.OutputField(desc="Your response to the user after completing the task") ``` --- diff --git a/nanocode.py b/nanocode.py index 925530d..c6313dd 100644 --- a/nanocode.py +++ b/nanocode.py @@ -121,6 +121,7 @@ def grep_files(pattern: str, path: str = ".") -> str: Matching lines in format 'filepath:line_num:content' """ print(f"{MAGENTA}⏺ Grep: {pattern}{RESET}") + regex = re.compile(pattern) hits = [] for filepath in globlib.glob(path + "/**", recursive=True): @@ -146,6 +147,7 @@ def run_bash(cmd: str) -> str: Command output (stdout and stderr combined) """ print(f"{MAGENTA}⏺ Bash: {cmd}{RESET}") + proc = subprocess.Popen( cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True )