Skip to content

YSMsimon/Warden

Repository files navigation

δΈ­ζ–‡η‰ˆ

Currently discounted. Expect bugs, incomplete features, and rough edges. Error handling and try/catch coverage will be improved over time.

A fully async, multi-agent CLI assistant with a custom adapter layer for LLM providers β€” swap between local Ollama models, OpenAI, DeepSeek, and more by changing one line in .env.

Demo

Simple Mode

5.16.mp4

Agent Architecture

                     Agent (base.py)
                          β”‚
         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
         β”‚                β”‚                β”‚
    MainAgent        PlannerAgent    ExecutorAgent
    (loop.py)        (planner.py)    (executor.py)
         β”‚
    EvaluatorAgent
    (evaluator.py)

All agents inherit from a shared Agent base class that handles the streaming LLM loop, tool execution, Rich.Live spinner/renderer, and depth limiting. Each subclass overrides only what it needs.


Adapter Architecture

adapters/
β”œβ”€β”€ __init__.py               # Adapter: unified entry point, routes by provider prefix
β”œβ”€β”€ schema.py                 # Response: canonical output format for all providers
β”œβ”€β”€ base_adapter.py           # Abstract interface all adapters implement
β”œβ”€β”€ openai_compat_adapter.py  # All OpenAI-compatible providers (DeepSeek, OpenAI, OpenRouter, etc.)
β”œβ”€β”€ ollama_adapter.py         # Ollama (OpenAI-compatible client + embed support)
β”œβ”€β”€ anthropic_adapter.py      # Anthropic
└── gemini_adapter.py         # Google Gemini

DeepSeek, OpenAI, and any external provider (OpenRouter, Together AI, Baidu AI, etc.) all share one adapter β€” OpenAICompatAdapter β€” since they all speak the OpenAI API. The router in __init__.py just passes the right api_key and base_url per provider. Only Ollama (embedding support), Anthropic, and Gemini need their own adapter.

Model strings use a provider/model format. The Adapter class reads the prefix, routes to the right provider adapter, and strips the prefix before the API call. All adapters return the same Response object:

class Response(BaseModel):
    content: str
    tool_calls: list[dict] = []
    reasoning: str = ''
    model: str = ''
    finish_reason: str = ''

Modes

/simple β€” Simple Mode

User Input
    β”‚
    β–Ό
MainAgent
    β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”œβ”€β”€β–Ί Tools: web_search, fetch_text,   β”‚
    β”‚  β”‚        read_file, write_file,    β”‚
    β”‚  β”‚        edit_file, run_bash,      β”‚
    β”‚  β”‚        grep, glob, ask_user,     β”‚
    β”‚  β”‚        to_do                     β”‚
    β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
    β”‚
    β–Ό
Response (streamed, rendered as Markdown)

Single agent loop with full tool access and user profile context. Fast and suitable for most tasks.


/deep β€” Deep Mode

User Input
    β”‚
    β–Ό
PlannerAgent ──► [ task1, task2, task3, ... ]
    β”‚
    β–Ό
ReasoningStep (main agent thinks through the plan)
  - Are tasks in the right order?
  - Dependencies between tasks?
  - Risks or constraints executors should know?
    β”‚
    β–Ό  (asyncio.gather β€” all run in parallel, with reasoning as context)
ExecutorAgent-1   ExecutorAgent-2   ExecutorAgent-N
    β”‚                  β”‚                  β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                        β”‚
                        β–Ό
                 EvaluatorAgent
                        β”‚
              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
            PASS                 FAIL (up to 3 rounds)
              β”‚                    β”‚
              β–Ό                    β–Ό
          Synthesize       ExecutorAgent x M (fix issues, parallel)
              β”‚                    β”‚
              β–Ό                    β–Ό
          Response          EvaluatorAgent (re-evaluate)

Deep mode adds a reasoning step between planning and execution. After the planner produces tasks, the main agent thinks through the plan β€” checking task order, dependencies, and risks β€” before executors start. This reasoning is passed to every executor as additional context. The prompt for this step lives in prompts/reasoning.md and can be edited to tune the behaviour.


Project Structure

.
β”œβ”€β”€ main.py                  # Entry point (async REPL, banner, prompt_toolkit session)
β”œβ”€β”€ requirements.txt
β”œβ”€β”€ Docker-compose.yml       # PostgreSQL + pgvector
β”œβ”€β”€ init.sql                 # DB schema (auto-run on first start)
β”œβ”€β”€ .env.example
β”‚
β”œβ”€β”€ adapters/
β”‚   β”œβ”€β”€ __init__.py          # Adapter: unified router by provider prefix
β”‚   β”œβ”€β”€ schema.py            # Response dataclass (canonical output format)
β”‚   β”œβ”€β”€ base_adapter.py      # Abstract base adapter interface
β”‚   β”œβ”€β”€ openai_compat_adapter.py  # DeepSeek, OpenAI, OpenRouter, etc.
β”‚   β”œβ”€β”€ ollama_adapter.py         # Ollama provider adapter (+ embeddings)
β”‚   β”œβ”€β”€ anthropic_adapter.py      # Anthropic provider adapter
β”‚   └── gemini_adapter.py         # Gemini provider adapter
β”‚
β”œβ”€β”€ agent/
β”‚   β”œβ”€β”€ base.py              # Base Agent: Rich.Live streaming, tool dispatch, depth limit
β”‚   β”œβ”€β”€ loop.py              # MainAgent: memory, profile, simple/deep routing, token summary
β”‚   β”œβ”€β”€ planner.py           # PlannerAgent: structured task decomposition
β”‚   β”œβ”€β”€ executor.py          # ExecutorAgent: executes one task with tools
β”‚   β”œβ”€β”€ evaluator.py         # EvaluatorAgent: structured pass/fail evaluation
β”‚   β”œβ”€β”€ compact.py           # Compactor: summarises conversation history
β”‚   └── profile.py           # ProfileManager: background user profiling
β”‚
β”œβ”€β”€ cli/
β”‚   β”œβ”€β”€ commands.py          # CLI command handler (/help, /model, /compact, etc.)
β”‚   β”œβ”€β”€ completions.py       # prompt_toolkit SlashCompleter + FillBgProcessor
β”‚   β”œβ”€β”€ renderer.py          # print_user_message (dark-bg submitted line)
β”‚   β”œβ”€β”€ theme.py             # Shared Rich Console + named theme styles
β”‚   └── diff.py              # Claude Code-style red/green diff renderer
β”‚
β”œβ”€β”€ common/
β”‚   └── config.py            # Loads .env and prompt files
β”‚
β”œβ”€β”€ memory/
β”‚   └── db.py                # asyncpg pool, history, knowledge, profiles
β”‚
β”œβ”€β”€ tools/
β”‚   β”œβ”€β”€ manager.py           # Tool definitions, named tool sets, styled confirmations
β”‚   β”œβ”€β”€ crawl.py             # web_search, fetch_text, fetch_html (PDF via pymupdf)
β”‚   β”œβ”€β”€ todo.py              # ToDoManager + to_do tool
β”‚   └── skill_manager.py     # Skills loader
β”‚
β”œβ”€β”€ prompts/
β”‚   β”œβ”€β”€ agent.md             # Main agent system prompt
β”‚   β”œβ”€β”€ subagent.md          # Subagent system prompt
β”‚   β”œβ”€β”€ planner.md           # Planner instructions
β”‚   β”œβ”€β”€ executor.md          # Executor instructions
β”‚   β”œβ”€β”€ evaluator.md         # Evaluator instructions
β”‚   β”œβ”€β”€ compact.md           # Compaction instructions
β”‚   └── profile.md           # Profile update instructions
β”‚
└── skills/                  # Skill definitions (SKILL.md + examples + templates)

Tool Access per Agent

Agent Tools
MainAgent (/simple) run_bash, read_file, write_file, edit_file, grep, glob, web_search, fetch_text, ask_user, to_do
ExecutorAgent (/deep) run_bash, read_file, write_file, edit_file, grep, glob, web_search, fetch_text
PlannerAgent none
EvaluatorAgent none

CLI Commands

Command Description
/help Show all available commands
/exit Exit the agent
/simple Switch to simple mode (single agent, fast)
/deep Switch to deep mode (multi-agent, thorough)
/profile View your current user profile
/delete-profile Delete your saved user profile
/clear-history Delete all conversation history
/compact Summarise and compress conversation history
/compact "focus on X" Compact with extra instructions
/context-window <n|off> Limit history to last N messages; off removes the limit
/model Show all current model settings
/model <provider/model> Change the main model (e.g. /model deepseek/deepseek-chat)
/model <role> <provider/model> Change a sub-model: compact, planner, evaluator, profile
/model all <provider/model> Set all models (main + all sub-models) to the same value
/apikey deepseek <key> Update DeepSeek API key β€” writes directly to .env
/apikey ollama <key> Update Ollama API key β€” writes directly to .env
/apikey external <key> Update external provider API key β€” writes directly to .env

Prerequisites

Dependency Why
Python 3.10+ Runs the agent
Docker Desktop Runs the PostgreSQL + pgvector database
Ollama Only needed if using local models
Node.js 18+ Only needed for WeChat ACP bridge

Setup

1. Clone and install Python dependencies

git clone https://github.com/YSMsimon/this-is-my-agent.git
cd this-is-my-agent
pip install -r requirements.txt

2. Start the database

docker compose up -d

This starts a PostgreSQL instance with the pgvector extension on port 5433. The init.sql file sets up the required tables automatically on first run.

3. Configure environment variables

cp .env.example .env

Edit .env β€” model strings use provider/model format:

# ── Models ───────────────────────────────────────────────────────────────────
MODEL=deepseek/deepseek-chat
EMBEDDING_MODEL=ollama/nomic-embed-text
PROFILE_MODEL=deepseek/deepseek-chat
COMPACT_MODEL=deepseek/deepseek-chat
PLANNER_MODEL=deepseek/deepseek-chat
EVALUATOR_MODEL=deepseek/deepseek-chat

# ── Database ─────────────────────────────────────────────────────────────────
DATABASE_URL=postgresql://myuser:mypassword@localhost:5433/agent_memory

# ── API keys (fill in the provider(s) you use) ───────────────────────────────
DEEPSEEK_API_KEY=...

# ── External provider (OpenRouter, Together AI, Baidu AI, etc.) ───────────────
# EXTERNAL_BASE_URL=https://openrouter.ai/api/v1
# EXTERNAL_API_KEY=sk-...

# ── Ollama (only if not on default localhost:11434) ───────────────────────────
# OLLAMA_BASE_URL=http://localhost:11434/v1

4. Run

python3 main.py

Model String Format

Model strings follow a provider/model format. The adapter layer reads the prefix, routes to the correct provider, and strips it before the API call.

Provider Format Example
Ollama ollama/model ollama/llama3.2
DeepSeek deepseek/model deepseek/deepseek-chat
OpenAI openai/model openai/gpt-4o
External external/model external/meta-llama/llama-3.1-70b-instruct

Environment Variables

Variable Description
MODEL Main chat model
EMBEDDING_MODEL Embedding model for RAG
PROFILE_MODEL Model for background profile updates
COMPACT_MODEL Model for history compaction
PLANNER_MODEL Model for task planning (deep mode)
EVALUATOR_MODEL Model for result evaluation (deep mode)
DATABASE_URL PostgreSQL connection string
DEEPSEEK_API_KEY DeepSeek API key
OPENAI_API_KEY OpenAI API key
OLLAMA_BASE_URL Custom Ollama URL (default: http://localhost:11434/v1)
OLLAMA_API_KEY Ollama API key (default: dummy)
EXTERNAL_BASE_URL Base URL for any OpenAI-compatible provider (e.g. OpenRouter, Together AI)
EXTERNAL_API_KEY API key for the external provider

WeChat ACP

Known issue: CLI commands (e.g. /exit, /help) do not currently work when sent through the WeChat ACP interface. Use the terminal REPL for commands.

1. Edit wechat-acp.config.json and replace the placeholder paths with your own:

{
  "agent": "python3 /YOUR/ABSOLUTE/PATH/this-is-my-agent/acp_agent.py",
  "cwd": "/YOUR/ABSOLUTE/PATH/this-is-my-agent"
}

2. Start the ACP bridge:

  • Mac/Linux: ./start-wechat-acp.sh
  • Windows (PowerShell): .\start-wechat-acp.ps1

Node.js must be installed for npx to work.

About

Warden is a Multi-agent CLI assistant with a custom LLM adapter layer. Swap between Ollama, DeepSeek, OpenAI, Anthropic, Gemini, and any OpenAI-compatible endpoint via one .env line. Deep mode runs a planner, parallel executors, and a self-evaluating loop. Memory stores in PostgreSQL.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages