Self-modifying AI agent that hot-reloads its own codeβbuilds itself as it runs.
One Python file that adapts to your environment, fixes itself, and expands capabilities at runtime.
Learn more: https://duck.nyc
| Feature | What You'll See | Demo |
|---|---|---|
| π₯ Hot-Reload | Agent detects code changes and restarts instantly | Watch |
| π Web UI | Clean web interface with real-time streaming | Watch |
| π οΈ Dynamic Tools | Save .py file in ./tools/ β use instantly |
Watch |
| π TCP Streaming | Connect via netcat, apps, or other agents | Watch |
| π IPC & Tray | macOS menu bar + Unix socket IPC | ![]() |
| π¬ Ambient Overlay | Floating AI input with glassmorphism UI | Watch |
# Install & run
pipx install devduck && devduck
# With speech-to-speech capabilities (optional)
pipx install "devduck[speech]" && devduck
# One-shot query
devduck "create a REST API with FastAPI"
# Python API
python -c "import devduck; devduck('analyze this code')"Requirements: Python 3.10-3.13, AWS credentials (or Ollama/Anthropic/GitHub/MLX)
Optional extras:
devduck[speech]- Real-time speech-to-speech conversations (Nova Sonic, OpenAI Realtime, Gemini Live)
| Feature | What It Does | How to Use |
|---|---|---|
| π₯ Hot-Reload | Auto-restarts on code changes | Edit __init__.py β saves β auto-restart |
| π οΈ Runtime Tools | Add/remove tools without restart | manage_tools(action="add", ...) |
| π¦ Dynamic Loading | Install packages and load tools on-the-fly | install_tools(action="install_and_load", package="...") |
| π§ Auto-RAG | Remembers conversations via Knowledge Base | Set DEVDUCK_KNOWLEDGE_BASE_ID |
| π Multi-Protocol | TCP, WebSocket, MCP, IPC servers | Auto-starts on ports 9999, 8080, 8000 |
| π MCP Client | Connect to external MCP servers | Set MCP_SERVERS env var |
| πΎ State Time-Travel | Save/restore agent state | state_manager(action="export") |
| π Self-Improvement | Updates own system prompt | system_prompt(action="add_context", ...) |
| βοΈ AWS Deploy | One-command serverless | agentcore_config(auto_launch=True) |
| π€ Speech-to-Speech | Real-time voice conversations | pip install devduck[speech] |
git clone git@github.com:cagataycali/devduck.git
cd devduck
python3.13 -m venv .venv
source .venv/bin/activate
# Basic install
.venv/bin/pip3.13 install -e .
# With speech capabilities
.venv/bin/pip3.13 install -e ".[speech]"
devduckgraph TB
A[User Input] -->|CLI/TCP/WS/MCP/IPC| B[DevDuck Core]
B -->|Auto RAG| C[Knowledge Base]
C -.->|Context Retrieval| B
B -->|Tool Calls| D[38+ Built-in Tools]
D --> E[shell/editor/calculator]
D --> F[GitHub/AgentCore]
D --> G[TCP/WebSocket/MCP/IPC]
D --> H[tray/ambient/cursor/clipboard]
B -->|Hot-reload| I[./tools/*.py + __init__.py]
I -.->|Load Instantly| D
B -->|Runtime| K[manage_tools/install_tools]
K -.->|Expand| D
B -->|Response| J[User Output]
J -.->|Store Memory| C
style B fill:#e1f5ff
style C fill:#d4edda
style I fill:#fff3cd
style K fill:#ffe6cc
Self-adapting loop: Query β RAG β Tools β Response β Memory β Hot-reload/Runtime-load β Repeat
DevDuck auto-detects providers based on credentials:
Priority: Bedrock β Anthropic β OpenAI β GitHub β Gemini β Cohere β Writer β Mistral β LiteLLM β LlamaAPI β MLX β Ollama
| Provider | API Key | Auto-Detected |
|---|---|---|
| Bedrock | AWS credentials | β
If boto3 auth succeeds |
| Anthropic | ANTHROPIC_API_KEY |
β If key present |
| OpenAI | OPENAI_API_KEY |
β If key present |
| GitHub | GITHUB_TOKEN or PAT_TOKEN |
β If key present |
| Gemini | GOOGLE_API_KEY or GEMINI_API_KEY |
β If key present |
| Cohere | COHERE_API_KEY |
β If key present |
| Writer | WRITER_API_KEY |
β If key present |
| Mistral | MISTRAL_API_KEY |
β If key present |
| LiteLLM | LITELLM_API_KEY |
β If key present |
| LlamaAPI | LLAMAAPI_API_KEY |
β If key present |
| MLX | No key needed | β On Apple Silicon (M1/M2/M3) |
| Ollama | No key needed | β Fallback if nothing else found |
Just set your API key - DevDuck handles the rest:
export ANTHROPIC_API_KEY=sk-ant-...
devduck # Auto-uses Anthropic
export OPENAI_API_KEY=sk-...
devduck # Auto-uses OpenAI
export GOOGLE_API_KEY=...
devduck # Auto-uses GeminiManual override:
export MODEL_PROVIDER=bedrock
export STRANDS_MODEL_ID=us.anthropic.claude-sonnet-4-20250514-v1:0
devduckAdd, remove, or reload tools while agent is running:
# List all loaded tools
manage_tools(action="list")
# Add tools from a package at runtime
manage_tools(action="add", package="strands_fun_tools", tool_names="cursor,clipboard,bluetooth")
# Remove tools you don't need
manage_tools(action="remove", tool_names="cursor,clipboard")
# Reload specific tools after editing
manage_tools(action="reload", tool_names="shell,editor")
# Reload all tools (restarts agent)
manage_tools(action="reload")
# Load custom tool from file
manage_tools(action="add", tool_path="./my_custom_tool.py")Install Python packages and load their tools at runtime:
# Discover available tools before loading
install_tools(action="list_available", package="strands-fun-tools", module="strands_fun_tools")
# Install package and load all tools
install_tools(action="install_and_load", package="strands-agents-tools", module="strands_tools")
# Install and load specific tools only
install_tools(
action="install_and_load",
package="strands-fun-tools",
module="strands_fun_tools",
tool_names=["clipboard", "cursor", "bluetooth"]
)
# Load tools from already installed package
install_tools(action="load", module="strands_tools", tool_names=["shell", "calculator"])
# List currently loaded tools
install_tools(action="list_loaded")Format: package1:tool1,tool2;package2:tool3,tool4
# Minimal (shell + editor only)
export DEVDUCK_TOOLS="strands_tools:shell,editor"
# Dev essentials
export DEVDUCK_TOOLS="strands_tools:shell,editor,file_read,file_write,calculator"
# Full stack + GitHub
export DEVDUCK_TOOLS="devduck.tools:tcp,websocket,mcp_server,use_github;strands_tools:shell,editor,file_read"
devduckCreate ./tools/weather.py:
from strands import tool
import requests
@tool
def weather(city: str) -> str:
"""Get weather for a city."""
r = requests.get(f"https://wttr.in/{city}?format=%C+%t")
return r.textEnable directory auto-loading:
export DEVDUCK_LOAD_TOOLS_FROM_DIR=true
devduck
# Save weather.py β use instantly (no restart needed)Default: Directory loading is OFF. Use manage_tools() or install_tools() for explicit control.
Install speech capabilities:
pip install "devduck[speech]"Real-time voice conversations with multiple providers:
# Start speech session with Nova Sonic (AWS Bedrock)
speech_to_speech(action="start", provider="novasonic")
# Start with OpenAI Realtime API
speech_to_speech(action="start", provider="openai")
# Start with Gemini Live
speech_to_speech(action="start", provider="gemini_live")
# Custom voice and settings
speech_to_speech(
action="start",
provider="novasonic",
model_settings={
"provider_config": {"audio": {"voice": "matthew"}},
"client_config": {"region": "us-east-1"}
}
)
# Stop session
speech_to_speech(action="stop", session_id="speech_20250126_140000")
# Check status
speech_to_speech(action="status")
# List conversation histories
speech_to_speech(action="list_history")
# List available audio devices
speech_to_speech(action="list_audio_devices")Supported Providers:
- Nova Sonic (AWS Bedrock): 11 voices (English, French, Italian, German, Spanish)
- OpenAI Realtime API: GPT-4o Realtime models
- Gemini Live: Native audio streaming
Environment Variables:
OPENAI_API_KEY- For OpenAI RealtimeGOOGLE_API_KEYorGEMINI_API_KEY- For Gemini Live- AWS credentials - For Nova Sonic (boto3 default credential chain)
Features:
- Background execution (parent agent stays responsive)
- Tool inheritance from parent agent
- Conversation history saved automatically
- Natural interruption with VAD
- Custom audio device selection
Claude Desktop (~/Library/Application Support/Claude/claude_desktop_config.json):
{
"mcpServers": {
"devduck": {
"command": "uvx",
"args": ["devduck", "--mcp"]
}
}
}Or start HTTP MCP server:
mcp_server(action="start", port=8000, stateless=True)
# Connect at: http://localhost:8000/mcpModes: --mcp (stdio for Claude Desktop) | http (background server) | stateless=True (multi-node)
Expand capabilities by loading tools from external MCP servers:
export MCP_SERVERS='{
"mcpServers": {
"strands-docs": {"command": "uvx", "args": ["strands-agents-mcp-server"]},
"remote": {"url": "https://api.example.com/mcp", "headers": {"Auth": "Bearer token"}},
"custom": {"command": "python", "args": ["my_server.py"]}
}
}'
devduckSupported transports: stdio (command/args/env) | HTTP (url/headers) | SSE (url with /sse path)
Tool prefixing: Each server's tools get prefixed (e.g., strands-docs_search_docs)
Save and restore agent state for reproducibility:
# Export current state
state_manager(action="export", metadata={"note": "before refactor"})
# List saved states
state_manager(action="list")
# Load and display state
state_manager(action="load", state_file="~/.devduck/states/devduck_20250118_150000.pkl")
# Resume from state (ephemeral - doesn't mutate parent)
state_manager(
action="resume",
state_file="~/.devduck/states/devduck_20250118_150000.pkl",
query="continue the analysis from where we left off"
)
# Modify state metadata
state_manager(
action="modify",
state_file="path/to/state.pkl",
metadata={"tags": ["important", "refactor"]}
)
# Delete state
state_manager(action="delete", state_file="path/to/state.pkl")States saved to: ~/.devduck/states/
Self-improvement - agent updates its own system prompt:
# View current system prompt
system_prompt(action="view")
# Add new context (appends to prompt)
system_prompt(action="add_context", context="New learning: Always use FastAPI for APIs")
# Update entire prompt
system_prompt(action="update", prompt="You are a specialized DevOps agent...")
# Sync to GitHub (persist across deployments)
system_prompt(
action="update",
prompt="Updated system prompt with new learnings...",
repository="cagataycali/devduck"
)
# Reset to default
system_prompt(action="reset")Pattern: Learn β Add context β Sync to GitHub β Persist forever
Automatic memory across sessions:
export DEVDUCK_KNOWLEDGE_BASE_ID=your_kb_id
devduckHow it works:
- Before each query: Retrieves relevant context from KB
- After each response: Stores conversation for future reference
- No manual tool calls needed - fully automatic
Manual storage:
store_in_kb(
content="Important information to remember...",
title="Project Context",
knowledge_base_id="optional-kb-id"
)Delegate tasks to specialized agents via GitHub Actions:
# Create sub-agent with specific model and tools
create_subagent(
repository="owner/repo",
workflow_id="agent.yml",
task="Analyze this dataset and provide insights",
model="us.anthropic.claude-sonnet-4-20250514-v1:0",
provider="bedrock",
max_tokens=60000,
tools="file_read,python_repl,calculator,http_request"
)
# Custom system prompt for specialized behavior
create_subagent(
repository="owner/repo",
workflow_id="agent.yml",
task="Review code and suggest improvements",
tools="file_read,editor,shell",
system_prompt="You are a senior code reviewer focused on best practices"
)
# Check sub-agent status
create_subagent(action="status", repository="owner/repo", workflow_id="agent.yml", run_id="12345")
# List recent runs
create_subagent(action="list", repository="owner/repo", workflow_id="agent.yml")π All Built-in Tools (39 total)
system_prompt- Update agent's system prompt (GitHub sync support)store_in_kb- Store content in Bedrock Knowledge Basestate_manager- Save/restore agent state (time-travel)tcp- TCP server with real-time streamingwebsocket- WebSocket server with concurrent messagingipc- Unix socket IPC server for local processesmcp_server- Expose as MCP server (HTTP/stdio)install_tools- Install packages and load tools at runtimecreate_subagent- Spawn sub-agents via GitHub Actionsuse_github- GitHub GraphQL API operationstray- System tray app control (macOS)ambient- Ambient AI input overlay (macOS)agentcore_config- Configure & launch on Bedrock AgentCoreagentcore_invoke- Invoke deployed AgentCore agentsagentcore_logs- View CloudWatch logs from agentsagentcore_agents- List/manage agent runtimesmanage_tools- Runtime tool add/remove/reloadview_logs- View/search/clear DevDuck logsspeech_to_speech- Real-time speech-to-speech conversations (optional - install withpip install devduck[speech])
shell- Interactive shell with PTY supporteditor- File editing (view/create/replace/insert/undo)file_read- Multi-file reading with search modesfile_write- Write content to filesfile_read- Read files with document mode for PDFs/CSVscalculator- SymPy-powered math (solve/derive/integrate)image_reader- Read images for Converse APIuse_agent- Nested agent with different modelload_tool- Load custom tools from Python filesenvironment- Environment variable managementmcp_client- Connect to external MCP servers autonomouslyretrieve- Bedrock Knowledge Base retrievalspeak- Text-to-speech (macOSsayor AWS Polly)slack- Slack messaging and event handling
listen- Background speech transcription (Whisper)cursor- Mouse & keyboard controlclipboard- Clipboard monitoring & controlscreen_reader- OCR & UI element detectionbluetooth- BLE scanning and GATT operationsyolo_vision- Object detection with YOLO
fetch_github_tool- Fetch and load tools from GitHub reposgist- Comprehensive GitHub Gist management (create/update/fork/star/comment)scraper- HTML/XML parsing with BeautifulSoup4add_comment- Add comments to GitHub issues/PRslist_issues- List GitHub repository issueslist_pull_requests- List GitHub repository PRs
Plus: Hot-reload tools from ./tools/ directory when DEVDUCK_LOAD_TOOLS_FROM_DIR=true
# ./tools/weather.py
from strands import tool
import requests
@tool
def weather(city: str) -> str:
"""Get weather for a city."""
r = requests.get(f"https://wttr.in/{city}?format=%C+%t")
return r.textSave β use instantly:
π¦ weather(city="Tokyo")
# Clear sky +15Β°CNo restart. No configuration. Just works.
| Protocol | Endpoint | Test Command | Use Case |
|---|---|---|---|
| CLI | Terminal | devduck "query" |
Interactive/one-shot |
| Python | Import | import devduck; devduck("query") |
Script integration |
| TCP | localhost:9999 |
nc localhost 9999 |
Network clients |
| WebSocket | localhost:8080 |
wscat -c ws://localhost:8080 |
Browser/async apps |
| MCP | localhost:8000/mcp |
Add to Claude Desktop | MCP clients |
| IPC | /tmp/devduck_main.sock |
nc -U /tmp/devduck_main.sock |
Local processes |
Custom ports:
export DEVDUCK_TCP_PORT=9000 DEVDUCK_WS_PORT=8001 DEVDUCK_MCP_PORT=8002
devduckDisable servers:
export DEVDUCK_ENABLE_TCP=false DEVDUCK_ENABLE_MCP=false
devduck| Variable | Default | Description |
|---|---|---|
| Model | ||
MODEL_PROVIDER |
Auto | Manual override: bedrock, anthropic, openai, github, gemini, cohere, writer, mistral, litellm, llamaapi, mlx, ollama |
STRANDS_MODEL_ID |
Auto | Model name (e.g., claude-sonnet-4, gpt-4o, qwen3:1.7b) |
| Provider API Keys | ||
ANTHROPIC_API_KEY |
- | Anthropic API key (auto-detected) |
OPENAI_API_KEY |
- | OpenAI API key (auto-detected) |
GOOGLE_API_KEY / GEMINI_API_KEY |
- | Google Gemini API key (auto-detected) |
GITHUB_TOKEN / PAT_TOKEN |
- | GitHub token for GitHub Models (auto-detected) |
COHERE_API_KEY |
- | Cohere API key (auto-detected) |
WRITER_API_KEY |
- | Writer API key (auto-detected) |
MISTRAL_API_KEY |
- | Mistral API key (auto-detected) |
LITELLM_API_KEY |
- | LiteLLM API key (auto-detected) |
LLAMAAPI_API_KEY |
- | LlamaAPI key (auto-detected) |
| Tools | ||
DEVDUCK_TOOLS |
39 tools | Format: package1:tool1,tool2;package2:tool3 |
DEVDUCK_LOAD_TOOLS_FROM_DIR |
false |
Auto-load from ./tools/ directory |
| Memory | ||
DEVDUCK_KNOWLEDGE_BASE_ID |
- | Bedrock KB ID for auto-RAG |
SYSTEM_PROMPT |
- | Additional system prompt content |
| MCP | ||
MCP_SERVERS |
- | JSON config for external MCP servers |
| Servers | ||
DEVDUCK_TCP_PORT |
9999 |
TCP server port |
DEVDUCK_WS_PORT |
8080 |
WebSocket server port |
DEVDUCK_MCP_PORT |
8000 |
MCP server port |
DEVDUCK_IPC_SOCKET |
/tmp/devduck_main.sock |
IPC socket path |
DEVDUCK_ENABLE_TCP |
true |
Enable TCP server |
DEVDUCK_ENABLE_WS |
true |
Enable WebSocket server |
DEVDUCK_ENABLE_MCP |
true |
Enable MCP server |
DEVDUCK_ENABLE_IPC |
true |
Enable IPC server |
| Speech | ||
BIDI_MODEL_ID |
Provider default | Override bidi model (e.g., amazon.nova-2-sonic-v1:0) |
| Context | ||
DEVDUCK_LOG_LINE_COUNT |
50 |
Recent log lines in context |
DEVDUCK_LAST_MESSAGE_COUNT |
200 |
Recent messages in context |
Ollama model not found:
# DevDuck auto-pulls models, but if it fails:
ollama pull qwen3:1.7bPort already in use:
# Change ports
export DEVDUCK_TCP_PORT=9000
export DEVDUCK_WS_PORT=8001
devduckHot-reload not working:
# Ensure tools directory exists
mkdir -p ./tools
# Check file watcher logs
devduck
π¦ view_logs(action="search", pattern="watcher")Memory/performance issues:
# Use lighter model
export STRANDS_MODEL_ID="qwen3:0.5b"
# Reduce context
export DEVDUCK_LOG_LINE_COUNT=20
export DEVDUCK_LAST_MESSAGE_COUNT=50Speech dependencies not found:
# Install speech extras
pip install "devduck[speech]"
# Or with pipx
pipx install "devduck[speech]"Ambient overlay not starting:
# Make sure tkinter is installed
python3 -c "import tkinter"
# Install tkinter if missing
brew install python-tk@3.13 # macOS
sudo apt-get install python3-tk # Ubuntu/Debian
sudo dnf install python3-tkinter # FedoraTray app not starting (macOS):
# Install rumps
pip install rumps
# Or reinstall devduck
pip install -e .View logs: devduck β π¦ view_logs()
Run DevDuck in CI/CD pipelines:
name: AI Code Assistant
on:
issues:
types: [opened, edited]
pull_request:
types: [opened, edited, synchronize]
jobs:
devduck:
runs-on: ubuntu-latest
permissions:
contents: read
issues: write
pull-requests: write
steps:
- uses: cagataycali/devduck@main
with:
task: "Analyze and help with this issue or PR"
provider: "github"
model: "gpt-4o"
tools: "shell,file_read,file_write,use_github,calculator"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}Sub-agent workflows:
devduck("Create a sub-agent to analyze test coverage")- Strands SDK: github.com/strands-agents/sdk-python
- Documentation: strandsagents.com
- Web UI: cagataycali.github.io/devduck
@software{devduck2025,
author = {Cagatay Cali},
title = {DevDuck: Self-Modifying AI Agent with Hot-Reload and Multi-Protocol Servers},
year = {2025},
url = {https://github.com/cagataycali/devduck}
}Apache 2.0 | Built with Strands Agents | @cagataycali
