Skip to content

Repository files navigation

OpenDev

OpenDev

OpenDev is a Python agentic coding CLI. It wraps the OpenAI SDK against an OpenRouter-compatible endpoint and drives a multi-turn agent loop with a built-in tool suite, a rich terminal UI, and pluggable extension points (custom tools, sub-agents, MCP servers, and lifecycle hooks).

◆ OpenDev  ·  mistralai/devstral  ·  ~/projects/app  ·  ● mcp 2/2      ctx ██████░░░ ~55%
────────────────────────────────────────────────────────────────────────────────
> refactor the auth module and run the tests

OpenDev.mp4


Features

  • Multi-turn agent loop with streaming output and tool calling.
  • Built-in toolsread_file, write_file, edit, shell, list_dir, grep, glob, web_search, web_fetch, todos, memory.
  • Sub-agents — delegate isolated tasks to specialized child agents (codebase_investigator, code_reviewer, or your own).
  • Custom tool discovery — drop a Tool subclass in .opendev/tools/ and it's auto-registered.
  • MCP integration — connect to Model Context Protocol servers (stdio or HTTP/SSE) and use their tools.
  • Context management — automatic compaction (LLM summary at ~80% of the window) and pruning of stale tool outputs.
  • Loop detection — catches repeated/cyclic actions and nudges the model out.
  • Approval system — policy-driven gate on mutating tools (reject dangerous commands, prompt for risky writes).
  • Hooks — run your own shell commands on agent/tool lifecycle events.
  • Session & checkpoint persistence — save, resume, checkpoint, and restore a conversation.
  • Non-blocking TUI — keep typing (and queue messages) while the agent works; slash commands; expandable sub-agent activity.

Install

Requires Python 3.11+.

pip install -r requirements.txt

Create a .env in the repo root with your credentials (gitignored):

API_KEY=sk-...
BASE_URL=https://openrouter.ai/api/v1
MODEL_NAME=mistralai/devstral-2505:free

main.py must be run from the repo root — imports are package-qualified.


Usage

# Interactive REPL
python main.py

# One-shot: run a single prompt and print the result
python main.py "list the largest files in this repo"

# Point at a different working directory
python main.py --cwd path/to/project

# Preview the terminal UI with sample content (no API calls)
python main.py --demo

Slash commands (interactive)

Command Action
/help list commands
/tools list available tools
/mcp show MCP server status
/clear clear the conversation
/compact summarize & shrink the context now
/context show message count in context
/stats token usage & turns
/model <name> switch the model
/approval <mode> on-request | auto | auto-edit | never | yolo
/config show current config
/save, /sessions, /resume <id> session persistence
/checkpoint [name], /checkpoints, /restore <id> checkpoints (conversation only)
/exit, /quit quit

Keys: type while the agent works to queue a message · Ctrl+C cancels the current turn (or, when idle, arms exit) · Ctrl+O expands the last sub-agent's activity · when an approval prompt is showing, a allows / d denies.


Configuration

Configuration is layered (later overrides earlier):

  1. System config — <user_config_dir>/opendev/config.toml
  2. Project config — <cwd>/.opendev/config.toml
  3. AGENTS.md in the cwd — loaded as developer instructions
  4. .env — credentials
  5. CLI flags — e.g. --cwd

Example .opendev/config.toml

# Model / behaviour
approval_policy = "on-request"   # on-request | on-failure | auto | auto-edit | never | yolo
max_turns = 50

[model]
name = "mistralai/devstral-2505:free"
temperature = 1.0

# An MCP server (stdio). Use `url = "..."` instead of command for HTTP/SSE.
[mcp_servers.filesystem]
enabled = true
command = "npx"
args = ["-y", "@modelcontextprotocol/server-filesystem", "."]

# Lifecycle hooks
hooks_enabled = true

[[hooks]]
name = "format-on-write"
trigger = "after_tool"          # before_agent | after_agent | before_tool | after_tool | on_error
command = "ruff format ."
timeout_sec = 30

Extending OpenDev

There are four extension seams — none require touching the core.

Custom tool.opendev/tools/my_tool.py:

from pydantic import BaseModel, Field
from tools.base import Tool, ToolInvocation, ToolKind, ToolResult

class MyParams(BaseModel):
    name: str = Field(..., description="who to greet")

class MyTool(Tool):
    name = "greet"
    description = "Greet someone by name."
    kind = ToolKind.READ
    schema = MyParams
    async def execute(self, invocation: ToolInvocation) -> ToolResult:
        p = MyParams(**invocation.params)
        return ToolResult.success_result(f"Hello, {p.name}!")

Custom sub-agent.opendev/agents/security_auditor.md:

---
name: security_auditor
description: Audits code for security vulnerabilities
tools: read_file, grep, glob, list_dir
max_turns: 15
---

You are a meticulous application-security reviewer. Report each finding with a
concrete path:line and a suggested fix.

MCP server — add an [mcp_servers.<name>] block (see config above).

Hook — add a [[hooks]] block. Hooks receive context via AI_AGENT_* env vars (AI_AGENT_TOOL_NAME, AI_AGENT_TOOL_PARAMS, AI_AGENT_RESPONSE, …). They observe and react (format, test, log, notify, back up) but cannot veto a tool call — that is the approval system's job.


Architecture

Config ─▶ CLI/TUI ─▶ Agent ─▶ Session ─┬─ ContextManager (history, compaction, pruning)
                                       ├─ ToolRegistry ─▶ builtin / discovered / MCP / sub-agent tools
                                       ├─ LLMClient (unified streaming contract, retry)
                                       ├─ ApprovalManager + HookSystem (gate/observe tool calls)
                                       ├─ LoopDetector + ChatCompactor (loop-break / summarize)
                                       └─ MCPManager (connect + register MCP tools)
  • LLMClient.chat_completion always returns AsyncGenerator[StreamEvent], so callers consume streaming and non-streaming identically.
  • Session uses a two-phase init: sync __init__ builds managers; async initialize() connects MCP, runs tool discovery, then builds the system prompt (which must list every tool).
  • Approval and hooks are woven into ToolRegistry.invoke, so every caller — including sub-agents — inherits them.

See notes.md for a deep dive (streaming internals, the agentic loop, context management, subagents, MCP, and an interview cheat-sheet) and CLAUDE.md for guidance when working in the codebase.


Project structure

agent/        Agent loop, Session, events, persistence
client/        LLMClient + typed StreamEvent schema
config/        Pydantic config + layered TOML loader
context/       ContextManager, ChatCompactor, LoopDetector
hooks/         HookSystem (lifecycle shell hooks)
prompts/       System-prompt assembly
safety/        Dangerous-command detection + ApprovalManager
subagents/     SubAgent + markdown loader
tools/         base.py, registry, discovery, builtin/, mcp/
ui/            Rich TUI
utils/         paths, text, errors
test/          Standalone test scripts (see test/README.md)
public/        Design diagrams
main.py        CLI entry point

Testing

Tests are self-contained assertion scripts (no pytest needed). Each resolves the repo root itself, so you can run them from anywhere:

python test/run_all.py        # everything (~273 checks across 15 suites)
python test/test_tools.py     # a single suite

See test/README.md for the full suite breakdown.


Status & limitations

Most of the planned feature set (public/FeatureList) is implemented. Known scope boundaries:

  • Hooks are fire-and-forget — they cannot block a tool call (use approval policies to block).
  • Checkpoints capture conversation state only — restoring does not revert file edits.
  • The approval prompt shows the target path/command, but not yet a full edit diff.
  • web_search/web_fetch require network access; the LLM client's retry path has no mocked unit test.

About

OpenDev is a Python agentic coding CLI. A multi-turn agent loop with a built-in tool suite, a rich terminal UI, and pluggable extension points (custom tools, sub-agents, MCP servers, and lifecycle hooks).

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages