Markdown-first runtime for AI agents
Write a .md file, describe what your agent should do, and let it run.
No boilerplate. No frameworks to learn. Just Markdown.
π Documentation β’ π Quick Start β’ π¦ Examples β’ π€ Contributing
- π One file = One agent β YAML frontmatter for config, Markdown body for the prompt
- β‘ Zero boilerplate β No classes, decorators, or complex frameworks
- π Flexible triggers β Run manually, on schedules (cron/intervals), or when files change
- π§ Built-in tools β File I/O (read, write, edit, move, delete, glob) and HTTP requests work out of the box
- π MCP support β Connect to any Model Context Protocol server
- π Execution tracking β Every run logged with status, duration, token usage, and estimated cost
- π€ Agent-to-agent delegation β Agents can call other agents with allowlist control and depth limits
- π‘οΈ Execution limits β Hard caps on tool calls, tokens, and cost to prevent runaway agents
- π Real-time SSE event stream β Subscribe to live execution and system events via
/events/stream - π― Git-friendly β Version control your prompts. See exactly how they evolved.
curl -fsSL https://raw.githubusercontent.com/z-fab/agentmd/master/install.sh | bashThis installs uv, agentmd, and runs the interactive setup wizard.
Windows (PowerShell):
irm https://raw.githubusercontent.com/z-fab/agentmd/master/install.ps1 | iexgit clone https://github.com/z-fab/agentmd.git
cd agentmd
uv sync
uv pip install -e ".[all]"
agentmd setupagentmd new hello-worldThis uses AI to generate the agent from a description you provide. Or use --template for an interactive questionnaire:
agentmd new hello-world --templateYou can also create agents manually β just add an .md file to agents/:
---
name: hello-world
---
You are a friendly assistant. When asked to execute your task,
write a creative greeting and save it to 'greeting.txt'.Note: No
modelneeded β Agentmd uses the default provider/model you configured during setup.
The agent's id is the filename stem (hello-world.md β hello-world). Optional frontmatter name: is only a display label (defaults to the id). Stems may include spaces and accented characters; quote them on the CLI:
agentmd run "Daily Processor"Each agent also has an icon shown in the CLI and the Obsidian plugin. Set it explicitly with icon: "π
" in frontmatter, or let agentmd derive a stable emoji from the id automatically.
Upgrading from β€0.15 where name: differed from the filename? That agent's history and memory are orphaned by the new identity model, and there is no automatic migration β see Migration v0.16.
agentmd run hello-worldOutput:
Execution 1 started
π§ >> file_write ({'path': 'greeting.txt', 'content': 'Hello! ...'})
π << file_write β Created greeting.txt (42 chars, 1 lines)
π€ I created a friendly greeting for you!
β
I created a friendly greeting and saved it to greeting.txt.
success | 1.8s | 160 tokens | $0.0001
Start a multi-turn conversation instead of a one-shot run:
agentmd chat hello-worldChat with hello-world (google/gemini-2.5-flash)
Type /exit to end the session
> Write me a greeting in Portuguese
file_write...
OlΓ‘! Que seu dia seja cheio de alegria e boas surpresas!
> Now save it to greeting-pt.txt
file_write...
Done! Saved to greeting-pt.txt.
> /exit
3 turns | 12.3s | 580 tokens | $0.0002
That's it! π
Agentmd uses two configuration files:
| File | Purpose |
|---|---|
~/.config/agentmd/config.yaml |
Application settings (paths, default model) β auto-created on first run |
~/agentmd/agents/_config/.env |
Secrets only (API keys) β workspace-level, overrides global .env |
# ~/.config/agentmd/config.yaml
workspace: ~/agentmd
agents_dir: agents
defaults:
provider: google
model: gemini-2.5-flash
max_tool_calls: 50 # optional: limit tool invocations per run
max_cost_usd: 1.00 # optional: cost cap per run (USD)# ~/agentmd/agents/_config/.env
GOOGLE_API_KEY=your-key-hereRun agentmd info to see the current effective configuration.
Run agents on intervals or cron schedules:
trigger:
type: schedule
every: 1h # or use cron: "0 9 * * *"Process files automatically as they appear:
trigger:
type: watch
paths: data/uploads/Switch LLM providers with a config change:
model:
provider: openai # google, anthropic, ollama, local
name: gpt-4oUse external tools via Model Context Protocol:
mcp:
- fetch # Web fetching
- github # GitHub APIβ See all examples in documentation
Comprehensive documentation is available at z-fab.github.io/agentmd
Quick Links:
- Quick Start
- Agent Configuration
- CLI Reference
- Execution Limits
- Providers
- Triggers
- Tools Documentation
- REST API
- Examples
- Security Best Practices
As of v0.8.0, agentmd start runs a FastAPI HTTP backend over a Unix domain socket. The CLI communicates with it automatically β no manual API calls needed for normal use.
agentmd start # foreground (Ctrl+C to stop)
agentmd start -d # background daemon
agentmd status # check backend status
agentmd stop # graceful shutdownThe backend exposes a full REST API for integrations. When --port and --api-key are provided, it also binds to TCP:
agentmd start --port 4100 --api-key YOUR_KEY
curl -H "X-API-Key: YOUR_KEY" http://127.0.0.1:4100/healthInteractive API docs are available at /docs (Swagger) and /redoc while the backend is running.
| Component | Technology |
|---|---|
| Runtime | Python 3.13+ |
| Agent Framework | LangGraph |
| LLM Providers | Google, OpenAI, Anthropic, Ollama, Local |
| HTTP Backend | FastAPI + Uvicorn |
| HTTP Client | HTTPX |
| CLI | Typer + Rich |
| Database | SQLite (async via aiosqlite) |
| Scheduling | APScheduler |
Contributions are welcome! Please open an issue or pull request on GitHub.
# Development setup
git clone https://github.com/z-fab/agentmd.git
cd agentmd
uv sync
uv pip install -e ".[all]"
agentmd setup # Interactive setup wizard
ruff format . # Format code
uv run pytest # Full suiteA hang looks like broken CI, not a red test. Follow the conventions in
tests/conftest.py:
- Prefer the shared
dbfixture (ortry/finally) β neverawait db.close()after assertions. aiosqlite's connection thread is non-daemon. - Bound stream/event waits with
asyncio.wait_for. - Stop backends via the internal shutdown path, not HTTP.
- Asserting inside
lifespan_context(...)is safe: shutdown runs in afinally.
pytest-timeout is intentionally not a project dependency: a global ceiling
masks hangs instead of fixing teardown, and wall-clock mutation checks catch
the real failure mode.
Always pass encoding="utf-8" to open(), read_text() and write_text(),
or open in binary mode. Without it Python uses the machine's locale, which is
UTF-8 here and on CI but cp1252 on a Windows console β so the bytes on disk
depend on who ran the command while every reader asks for UTF-8. That is how
agentmd setup came to write a .env it could not read back, taking every
later command down with it. tests/test_no_implicit_encoding.py walks the
package AST and fails on any new offender.
MIT License β use it, fork it, build on it.
Built with β€οΈ and Markdown
If agents could write themselves, they'd choose Markdown too.