RegCode
A minimalistic coding agent with Python API bindings and CLI interface.
Features
- litellm as the LLM backbone (supports OpenAI, Anthropic, and 100+ providers)
- YAML configuration for model, tokens, temperature, and provider settings via Pydantic models
- Environment variable expansion in config values (e.g.,
${OPENAI_API_KEY}) - CLI via click:
chat,configure,version - Python API:
import regcode; agent = regcode.Agent() - Host filesystem I/O:
write_fileandpatch_fileoperate directly on the host filesystem with path traversal protection - Robust tool call handling: malformed tool calls and JSON parse errors are gracefully handled
- Progressive streaming: TUI always streams assistant responses progressively rather than all at once
- Context window compaction: Automatically compacts conversation history when approaching token limits, preserving system instructions and review notes
- Review notes: Persistent findings stored across context compaction sessions
- Tool budget limits: Configurable maximum number of tool calls per conversation turn
Quick Start
# Install dependencies
uv sync
# Configure your API key (or set OPENAI_API_KEY in your environment)
# Edit config.yaml or use environment variables
# Chat with the agent (uses Textual TUI)
uv run regcode chat
# Interactive config generator
uv run regcode configure
# View version
uv run regcode version
CLI Chat Options
# Full agent mode (enables write permissions)
uv run regcode chat --full
# Use a specific model
uv run regcode chat --model "openai/gpt-4o"
# Disable colored output
uv run regcode chat --no-color
# Use a custom config file
uv run regcode chat --config /path/to/config.yaml
TUI Slash Commands
While chatting, you can use these slash commands in the input bar:
/exitor/quit— Quit the application/clear— Clear the chat history/reset— Reset the session (clear history and tools)
Usage as a Library
import regcode
# Load config (reads config.yaml, falls back to ~/.regcode/config.yaml, then defaults)
config = regcode.Config.load("config.yaml")
# Create agent
agent = regcode.Agent(config=config)
# Chat (TUI always streams responses progressively)
reply = agent.chat("Refactor this function to use async/await.")
print(reply)
# Reset conversation
agent.reset()
Tools
RegCode includes several built-in tools the agent can use:
| Tool | Permission | Description |
|---|---|---|
python_code |
EXECUTE |
Execute Python code in an isolated Monty sandbox with Rust-based security and resource limits |
shell_command |
EXECUTE |
Execute shell commands (subprocess, not sandbox). Only safe, read-only commands are allowed (ls, cat, echo, head, tail, wc, grep, sort, uniq, date, pwd, whoami, id, uname, df, free, which, type, stat, file, sha256sum, md5sum, base64, xxd, od, hexdump, find) |
run_script |
EXECUTE |
Execute a Python script file in the Monty sandbox |
read_file |
READ |
Read the contents of a file from the filesystem |
write_file |
WRITE |
Write content to a file on the host filesystem |
patch_file |
WRITE |
Replace a range of lines in an existing file |
list_dir |
READ |
List contents of a directory |
search_files |
READ |
Search for files matching a pattern |
search_dir |
READ |
Search for a pattern inside all files within a directory |
fetch_git_diff |
READ |
Fetch the git diff of the current repository |
osv_cve_scan |
NETWORK |
Scan project dependencies for known CVEs via OSV.dev API. Optionally scans the codebase for vulnerable usage patterns |
web_search |
NETWORK |
Search the web via a SearXNG instance |
web_crawl |
NETWORK |
Crawl a URL and extract its content as markdown |
delete_file |
DELETE |
Delete a file from the host filesystem |
delete_directory |
DELETE |
Delete a directory tree on the host filesystem |
add_review_note |
READ |
Save a finding that survives context compaction (in-memory scratchpad, not the host) |
read_review_notes |
READ |
Read all saved review notes |
system_info |
(none) | Get system information (OS, Python version, CPU, memory). No filesystem, network or subprocess access |
Every tool declares the permission it requires as a class attribute. The registry
refuses to register a tool that declares neither a permission nor None, so a newly
added tool cannot quietly default its way into every agent. Filtering iterates the
registry, not a lookup table — previously a tool missing from a hand-maintained map
(delete_file, delete_directory, web_search, web_crawl and others among them)
could not be disabled by any permission setting.
Note:
browse_diris defined in the codebase but not included in the default tools. It can be explicitly registered if needed, but is excluded by default because it recursively lists all files in a directory, which can fill the context window in large projects (e.g.,.venv,node_modules).
All file-writing tools (write_file, patch_file) include path traversal protection (.. is blocked). The patch_file tool validates line number bounds and type correctness before performing the replacement.
Working directory and confinement
Agent(directory=...) (and the CLI's --directory, which defaults to the current
directory) is the root every tool is confined to:
- Every path-like tool argument (
path,file_path,script_path,directory) is resolved against it and rejected when it resolves outside — absolute paths and..escapes alike. shell_commandruns withcwdset to it, and its arguments are checked the same way, socat /etc/passwdis rejected even thoughcatis allowlisted.python_codeandrun_scriptexecute inside the Monty sandbox, which has no host filesystem access at all.
directoryis a confinement boundary, not a security sandbox.shell_commandstill runs a real subprocess as the current user, and the toolset is only as safe as its permission list. Services embedding RegCode behind an authenticated endpoint should restrictpermissions(or unregister tools) rather than relying ondirectoryalone.
Project Structure
project-root/
├── main.py # Unused stub (hello world only)
├── config.yaml # Runtime configuration
├── config.yaml.template # Template for new configs
├── pyproject.toml # Project metadata and dependencies
├── uv.lock # Locked dependencies
└── regcode/ # Python package
├── __init__.py # Python API entry point
├── main.py # Agent core (chat, review, etc.)
├── cli.py # CLI entry point (click)
├── config.py # Config loader (yaml + pydantic)
├── permissions.py # Tool permission definitions
├── tui.py # Textual-based terminal UI
├── conversation_manager.py # Context compaction & review notes
├── sandbox.py # Legacy sandbox support
├── monty_sandbox.py # Monty sandbox integration
└── tools/
├── __init__.py
├── base.py # Base tool classes
├── builtins.py # Built-in tool implementations
├── registry.py # Tool registry management
└── review_notes.py # Review notes persistence
├── tests/
│ ├── conftest.py
│ ├── test_main.py
│ ├── test_config.py
│ ├── test_api.py
│ ├── test_cli.py
│ ├── test_context_manager.py
│ ├── test_monty_sandbox.py
│ ├── test_provider_extra.py
│ └── test_review_notes.py
└── README.md
Testing & Linting
uv run pytest tests/ -v
uv run ruff check --fix regcode/ tests/
Configuration
Edit config.yaml to customize the agent's behavior. The config is loaded from the specified path, ~/.regcode/config.yaml, or falls back to defaults.
agent:
context_window: 128000 # Maximum context window size
compaction_threshold: 0.8 # Compact when context > 80% of window
enable_compaction: true # Enable automatic context compaction
provider:
model: "openai/gpt-4o" # Model identifier (litellm format)
base_url: "https://api.openai.com/v1"
api_key: "${OPENAI_API_KEY}" # Environment variable expansion supported
temperature: 1.0
max_tokens: 4096
system_prompt: | # Custom system prompt (default is provided)
You are a coding agent.
tools: {} # Reserved for future tool configuration
sandbox:
use_monty: true # Enable Monty sandbox for Python execution
max_duration_secs: 10.0
max_memory_mb: 64
max_recursion_depth: 100
type_check: true
allowed_imports: []
tool_budget: 20 # Max tool executions per user turn
truncate_on_length: true # Raise when a completion is cut off at max_tokens with no output
permissions: # Default: read, execute, delete, network
- read # READ: read_file, list_dir, search_files, search_dir, fetch_git_diff, add_review_note, read_review_notes
- execute # EXECUTE: python_code, shell_command, run_script
- delete # DELETE: delete_file, delete_directory
- network # NETWORK: web_search, web_crawl, osv_cve_scan
# - write # WRITE: write_file, patch_file
# - all # ALL: grants every permission
Permission Levels
| Permission | Tools Granted |
|---|---|
read |
read_file, list_dir, search_files, search_dir, fetch_git_diff, add_review_note, read_review_notes |
write |
write_file, patch_file |
delete |
delete_file, delete_directory |
execute |
python_code, shell_command, run_script |
network |
web_search, web_crawl, osv_cve_scan |
all |
All permissions |
system_info requires no permission and is always available. browse_dir requires
READ but is not registered by default.
Why
deleteandnetworkare in the default list. They are not a widening of what regcode used to allow —delete_file,delete_directory,web_search,web_crawlandosv_cve_scanwere already reachable under the old[read, execute]default because they were missing from the permission map and so could not be disabled. The default now names the toolset it always had.writeremains absent, sowrite_fileandpatch_fileare still off unless you (orchat --full) ask for them.
Key Settings
context_window: Maximum token budget for the conversation context.compaction_threshold: Ratio of context window at which compaction triggers (e.g.,0.8= compact at 80%).tool_budget: Maximum number of tool executions in a single user turn. Distinct fromchat(max_tool_calls=...), which bounds LLM turns in one run — since a turn often emits two or three tool calls,tool_budgetis usually the tighter ceiling. Settool_budget >= max_tool_callsif you pass both.permissions: List of permission levels granted to the agent. Thechat --fullCLI flag automatically addsWRITEpermission.truncate_on_length: RaiseTruncatedResponseErrorwhen a completion stops atprovider.max_tokenshaving produced neither text nor a tool call. The CLI sets this tofalseso an interactive turn warns instead of raising.
Embedding regcode in a server
Config() and Config.load() look alike and are not the same thing:
| Constructor | Reads a config file? | Permissions |
|---|---|---|
Config() / Config.defaults() |
No — pure field defaults | read, execute, delete, network |
Config.load() |
./config.yaml, then ~/.regcode/config.yaml |
whatever the file says |
Config.for_server() |
No | read only |
Agent.__init__ is config or Config.load(), so passing any Config bypasses file
loading entirely. A limit you observe at runtime came from your own code the moment
you pass a Config — never from a user's ~/.regcode/config.yaml. Use
Config.defaults() when you mean "field defaults" and Config.load() when you mean
"the user's settings"; the cwd-relative ./config.yaml lookup wins over the home
directory, so Config.load() depends on where the process was started.
For a server, start from Config.for_server() rather than Config():
from regcode import Agent, Config, TruncatedResponseError
config = Config.for_server(
tool_budget=40, # >= max_tool_calls you intend to pass
)
config.provider.model = "hosted_vllm/my-model"
config.provider.api_key = "..."
config.provider.max_tokens = 32000 # reasoning models need headroom for the answer
agent = Agent(config=config, directory="/srv/work/clone")
try:
answer = agent.chat(message=task, max_tool_calls=25)
except TruncatedResponseError:
# The model spent the whole budget reasoning and emitted nothing.
# Retry with a larger provider.max_tokens.
...
for_server() restricts permissions to [READ], which removes every tool that can
execute, write, delete or reach the network, and turns off debug_mode and tracing.
The fields a host is responsible for:
permissions—for_server()gives you[READ]; add back only what a given job needs. Verify by inspectingagent.registry.enabled_tools, not the config: that is what the model is actually offered.provider.max_tokens— regcode's default is4096. A reasoning model can spend all of it on chain-of-thought and return an empty final answer; that now raisesTruncatedResponseErrorinstead of returning"".tool_budget— default20tool executions per user turn, reset eachchat()call. A review of a large diff stops reading files at 20 with no signal unless you raise it.agent.context_window— default128000; compaction triggers at(context_window - provider.max_tokens) * compaction_threshold.directory— confine every tool to one root. See Working directory and confinement.
MCP servers are gated too: set mcp_servers.<name>.permission to
read/write/delete/execute/network to require it, or leave it unset to keep
the server's tools ungated (adding the server is itself the opt-in).
Environment Variables
Values in config.yaml can reference environment variables using ${VAR_NAME} syntax. For example:
provider:
api_key: "${OPENAI_API_KEY}"
Supported formats: ${VAR}, ${VAR:-default}, and standard shell variable expansion.
License
MIT
Metadata
Release files for regcode 0.13.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| regcode-0.13.0.tar.gz | 524.0 kB | Without provenance | Details for regcode-0.13.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| regcode-0.13.0-py3-none-any.whl | Python 3 | none | any | Details for regcode-0.13.0-py3-none-any.whl |
Total release size: 621.4 kB
Release files / regcode-0.13.0.tar.gz
| Download URL | regcode-0.13.0.tar.gz |
|---|---|
| Size | 524.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
6bb59c79fa643396911067db69481b9dd23857297044e562511e758e02059e80
|
|
BLAKE2b-256 checksum How to use checksums |
50a3003da3325aaaa5f4f3aa9904ff615df722a9d05fb8f94db45e7dda583e66
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"13","id":"trixie","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / regcode-0.13.0-py3-none-any.whl
| Download URL | regcode-0.13.0-py3-none-any.whl |
|---|---|
| Size | 97.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
747521fbaa607f4e7432c27af12d3739a155f9aeaecf9e5c1455070e24fad246
|
|
BLAKE2b-256 checksum How to use checksums |
99f40eb06e6e8f6c495686bcc9931eae9ecea5ce4d5c547b4e0d3c85b3e98e03
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"13","id":"trixie","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Security for regcode 0.13.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.