Introduction
Duragent — A durable, self-contained runtime for AI agents.
Sessions survive crashes. Agents are just files. One binary, zero dependencies.
Use it as a personal AI assistant, or as the foundation for agent-powered products.
Why Duragent?
| What you get | How |
|---|---|
| Sessions that survive crashes | Append-only event log, attach/detach like tmux |
| Agents you can read and version | YAML + Markdown — no code required |
| State you can inspect | Just files on disk — cat, grep, git diff |
| Deploy anywhere | Single binary, ~10MB, no Python/Node/Docker |
| Your choice of parts | Swap LLM providers, gateways, and storage backends or bring your own |
Features
- Durable sessions — crash, restart, reconnect; your conversation survives
- Portable agent format — define agents in YAML + Markdown; inspect, version, and share them
- Memory — agents recall past conversations, remember experiences, and reflect on long-term knowledge
- Tools — bash execution, CLI tools, and scheduled tasks, with configurable approval policies
- Skills — modular capabilities defined as Markdown files (Agent Skills standard)
- Multiple LLM providers — Anthropic, OpenAI, OpenRouter, Ollama
- Platform gateways — Telegram and Discord via subprocess plugins
- HTTP API — REST endpoints with SSE streaming
Modular by Design
| Component | Default | Swappable |
|---|---|---|
| Gateways | CLI, HTTP, SSE, Telegram, Discord | Any platform via gateway plugins |
| LLM | OpenRouter | Anthropic, OpenAI, Ollama, or any provider |
| Sandbox | Trust mode | bubblewrap, Docker (planned) |
| Storage | Filesystem | Postgres, S3 (planned) |
Installation
Requirements
- Rust 1.85+ (stable toolchain)
- Linux, macOS, or Windows
From Source
git clone https://github.com/giosakti/duragent.git
cd duragent
make build
./target/release/duragent --version
Cargo Install
cargo install --git https://github.com/giosakti/duragent.git
Verify Installation
duragent --version
Gateway Plugins
Gateway plugins (Telegram, Discord) are separate binaries. To install them:
# Discord gateway
cargo install --git https://github.com/giosakti/duragent.git duragent-gateway-discord
# Telegram gateway
cargo install --git https://github.com/giosakti/duragent.git duragent-gateway-telegram
Authentication
Duragent supports multiple LLM providers. Each provider requires credentials — either an API key set as an environment variable or, for Anthropic, an OAuth login.
Methods
| Method | Providers | Storage | Auto-Refresh |
|---|---|---|---|
| OAuth login | Anthropic only | ~/.duragent/auth.json | Yes |
| Environment variable | All providers | Shell environment | No |
When both are configured for the same provider, OAuth credentials take precedence over the environment variable.
OAuth Login (Anthropic)
duragent login anthropic
This starts a browser-based OAuth flow (PKCE):
- A URL is printed — open it in your browser
- Authorize the application on Anthropic’s site
- Copy the returned code and paste it back into the terminal
- Tokens are saved to
~/.duragent/auth.json(mode 0600, user-only read/write)
Tokens are automatically refreshed when they expire. You do not need to run duragent login again unless the refresh token is revoked.
Security: Never commit
~/.duragent/auth.jsonto version control.
Environment Variables
For providers that don’t support OAuth login, set the API key as an environment variable:
| Variable | Provider |
|---|---|
ANTHROPIC_API_KEY | Anthropic (Claude) |
OPENROUTER_API_KEY | OpenRouter |
OPENAI_API_KEY | OpenAI-compatible providers |
At least one provider must be configured for agents to function. Ollama does not require an API key (local inference).
# Example: OpenRouter
export OPENROUTER_API_KEY=your-key
duragent serve
Per-Provider Setup
Anthropic
Recommended: Use OAuth login for automatic token refresh.
duragent login anthropic
Alternative: Set the API key manually.
export ANTHROPIC_API_KEY=sk-ant-...
Agent configuration:
spec:
model:
provider: anthropic
name: claude-sonnet-4-20250514
OpenRouter
export OPENROUTER_API_KEY=your-key
spec:
model:
provider: openrouter
name: anthropic/claude-sonnet-4
OpenAI
export OPENAI_API_KEY=sk-...
spec:
model:
provider: openai
name: gpt-4o
Ollama (Local)
No API key needed. Ollama must be running locally.
spec:
model:
provider: ollama
name: llama3
base_url: http://localhost:11434
Credential Precedence
For Anthropic, credentials are resolved in this order:
- OAuth token from
~/.duragent/auth.json(if present and valid) ANTHROPIC_API_KEYenvironment variable
For all other providers, only the environment variable is checked.
Troubleshooting
| Problem | Solution |
|---|---|
“Unsupported provider” from duragent login | Only anthropic supports OAuth. Use environment variables for other providers. |
| OAuth token expired | Tokens auto-refresh. If the refresh token is revoked, run duragent login anthropic again. |
| “No LLM provider configured” | Set at least one API key or run duragent login anthropic. |
| Credentials not taking effect | Restart the server after changing environment variables or running duragent login. |
Quick Start
This guide walks you through creating your first agent, starting the server, and chatting with it.
1. Initialize a Workspace
duragent init
# Follow the interactive setup
This creates a .duragent/ directory with a starter agent and configuration.
2. Authenticate and Start the Server
Set up credentials for your LLM provider, then start the server:
# Option A: OAuth login (Anthropic only — tokens auto-refresh)
duragent login anthropic
# Option B: API key via environment variable
export OPENROUTER_API_KEY=your-key
duragent serve
See Authentication for details on all providers.
3. Chat with Your Agent
duragent chat --agent <YOUR_AGENT_NAME>
Type your message and press Enter. The agent will respond using the configured LLM.
4. Attach to a Session Later
Sessions are durable — you can disconnect and reconnect at any time:
duragent attach --list # List attachable sessions
duragent attach SESSION_ID # Reconnect to existing session
What’s Next?
- Learn about the Agent Format to customize your agents
- Add Tools to give your agent capabilities
- Set up Telegram or Discord to chat from messaging platforms
- Configure Memory for persistent agent knowledge
Agents
An agent in Duragent is defined by a set of files — YAML for structure, Markdown for prose. No code required.
Agent Directory
Each agent lives in its own directory under .duragent/agents/:
.duragent/agents/my-assistant/
├── agent.yaml # Agent definition (required)
├── SOUL.md # "Who the agent IS" (identity and personality)
├── SYSTEM_PROMPT.md # "What the agent DOES" (core system prompt)
├── INSTRUCTIONS.md # Additional runtime instructions (optional)
├── policy.yaml # Tool execution policy (optional)
├── skills/ # Skill definitions (optional)
│ └── task-extraction/
│ └── SKILL.md
├── memory/ # Agent's long-term memory
│ ├── MEMORY.md
│ └── daily/
└── tools/ # CLI tools for this agent (optional)
└── my-tool.sh
Minimal Agent
The simplest agent needs just two files:
# agent.yaml
apiVersion: duragent/v1alpha1
kind: Agent
metadata:
name: simple-assistant
spec:
model:
provider: openrouter
name: anthropic/claude-sonnet-4
system_prompt: ./SYSTEM_PROMPT.md
<!-- SYSTEM_PROMPT.md -->
You are a helpful assistant.
Prompt Files
Duragent separates agent prompts into three optional files:
| File | Purpose | When Loaded |
|---|---|---|
SOUL.md | “Who the agent IS” — identity and personality | Every turn |
SYSTEM_PROMPT.md | “What the agent DOES” — core system prompt | Every turn |
INSTRUCTIONS.md | Additional runtime instructions | Every turn |
Best practice: Keep SYSTEM_PROMPT.md minimal. Put detailed behavioral rules in INSTRUCTIONS.md and situational context in skills (loaded on demand).
Agent Loading
Duragent supports two loading modes:
| Mode | Source | Use Case |
|---|---|---|
| Static | Files in .duragent/agents/ | GitOps, version-controlled agents |
| Dynamic | Admin API | API-deployed, multi-tenant |
Agents are lazily loaded with an LRU cache — you can have thousands of agent definitions without memory overhead.
Agent Routing
When a message arrives from a gateway, Duragent uses routing rules to determine which agent handles it. See Configuration > Routes for details.
For more on agent definition, see the Agent Format guide.
Sessions
Sessions are the core durability primitive in Duragent. A session is a persistent conversation context that survives crashes, restarts, and disconnects.
Why Sessions Matter
| Without Sessions | With Sessions |
|---|---|
| Crash = context lost | Crash = reconnect and continue |
| One client only | Access from CLI, HTTP, Telegram, web |
| Tied to terminal | Portable across devices |
| No history API | Query conversation history programmatically |
Session States
| State | Description |
|---|---|
active | Running, client connected |
running | Running, client disconnected (continue mode) |
paused | Waiting for reconnect (pause mode) |
completed | Completed or explicitly ended |
Disconnect Behavior
When a client disconnects, the agent’s behavior is configurable per-agent:
| Mode | Behavior | Use Case |
|---|---|---|
continue | Agent keeps executing, buffers output | Async workflows, fire-and-forget tasks |
pause | Agent pauses, waits for reconnect | Interactive chat |
# agent.yaml
spec:
session:
on_disconnect: pause # or: continue
Multi-Gateway Access
Sessions are accessible from any gateway — the same session can be started from CLI, continued via Telegram, and queried via HTTP API.
Storage
Each session is stored as files:
.duragent/sessions/{session_id}/
├── events.jsonl # Append-only event log
└── state.yaml # Snapshot for fast resume
- events.jsonl — Every message, tool call, and status change is appended here
- state.yaml — Periodic snapshot for fast resume (no need to replay all events)
Session Lifecycle
TTL and Expiry
Sessions can be configured to expire after a period of inactivity:
# duragent.yaml
sessions:
ttl_hours: 168 # 7 days (default). 0 disables auto-expiry.
Per-agent TTL overrides are also supported in agent.yaml.
Event Log Compaction
To prevent unbounded growth, Duragent compacts the event log after each snapshot:
# duragent.yaml
sessions:
compaction: discard # discard (default) | archive | disabled
| Mode | Behavior |
|---|---|
discard | Remove events covered by the snapshot |
archive | Move old events to events.archive.jsonl before removing |
disabled | No compaction (events grow unbounded) |
Gateway Commands
In gateway chats (Telegram, Discord), you can use slash commands:
| Command | Behavior |
|---|---|
/reset | End current session; next message starts fresh |
/status | Show current session info |
Gateways
Gateways handle communication between users and agents. Duragent separates core gateways (built-in) from platform gateways (plugins).
Core Gateways (Built-in)
These protocols ship with Duragent:
| Gateway | Use Case |
|---|---|
| CLI | Local development, duragent chat, duragent attach |
| HTTP REST | Programmatic access, webhooks, Admin API |
| SSE | Real-time LLM token streaming |
Platform Gateways (Plugins)
Platform-specific integrations (Telegram, Discord, etc.) run as separate processes communicating via the Gateway Protocol — JSON Lines over stdio.
┌──────────────────────────────────┐
│ Duragent │
│ ┌────────────────────────────┐ │
│ │ Core Gateways (built-in) │ │
│ │ CLI │ HTTP REST │ SSE │ │
│ └────────────────────────────┘ │
│ │ │
│ Gateway Protocol │
│ (JSON Lines / stdio) │
│ │ │
└──────────────┼───────────────────┘
│
┌──────────┼──────────┐
▼ ▼ ▼
[Telegram] [Discord] [others]
(plugin) (plugin) (plugin)
This design means:
- Gateway crashes don’t affect core — plugins are isolated processes
- Any language — plugins can be written in Rust, Python, Go, etc.
- Independent releases — plugins ship separately from Duragent core
- Lightweight core — platform SDKs aren’t bundled in the main binary
Gateway Protocol
Plugins communicate with Duragent using JSON Lines over stdin/stdout:
| Direction | Message Types |
|---|---|
| Duragent to plugin | send_message, send_media, send_typing, edit_message, delete_message, answer_callback_query, ping, shutdown |
| Plugin to Duragent | ready, message_received, callback_query, command_ok, command_error, pong, error, auth_required, auth_success, shutdown |
First-party plugins (Telegram, Discord) are written in Rust and ship as standalone binaries. Third-party plugins can use any language.
Available Gateways
| Gateway | Status | Crate |
|---|---|---|
| CLI | Built-in | duragent |
| HTTP REST | Built-in | duragent |
| SSE | Built-in | duragent |
| Telegram | Plugin | duragent-gateway-telegram |
| Discord | Plugin | duragent-gateway-discord |
For setup instructions, see Gateway Setup. For configuration details, see Gateway Plugins.
Agent Format
The Duragent Format defines a portable, human-readable specification for AI agents. Agents are defined with YAML for structure and Markdown for prose — no code required.
Directory Layout
.duragent/agents/my-agent/
├── agent.yaml # Agent definition (required)
├── SOUL.md # "Who the agent IS" (identity and personality)
├── SYSTEM_PROMPT.md # "What the agent DOES" (core system prompt)
├── INSTRUCTIONS.md # Additional runtime instructions (optional)
├── policy.yaml # Tool policy (optional, version controlled)
├── policy.local.yaml # User policy overrides (gitignored)
├── skills/ # Skills directory (optional)
│ └── task-extraction/
│ └── SKILL.md
├── memory/ # Agent's long-term memory
│ ├── MEMORY.md
│ └── daily/
└── tools/ # CLI tools (optional)
└── my-tool.sh
Agent Definition
Minimal Example
apiVersion: duragent/v1alpha1
kind: Agent
metadata:
name: simple-assistant
spec:
model:
provider: openrouter
name: anthropic/claude-sonnet-4
system_prompt: ./SYSTEM_PROMPT.md
Full Example
apiVersion: duragent/v1alpha1
kind: Agent
metadata:
name: productivity-assistant
description: Personal productivity assistant with task management
version: 1.0.0
labels:
domain: productivity
spec:
model:
provider: openrouter
name: anthropic/claude-sonnet-4
temperature: 0.7
max_output_tokens: 4096
soul: ./SOUL.md
system_prompt: ./SYSTEM_PROMPT.md
instructions: ./INSTRUCTIONS.md
session:
on_disconnect: continue
max_tool_iterations: 10
ttl_hours: 48
compaction: archive
context:
max_history_tokens: 20000
max_tool_result_tokens: 8000
access:
dm:
policy: open
groups:
policy: allowlist
allowlist: ["telegram:-100123456"]
activation: mention
memory:
backend: filesystem
tools:
- type: builtin
name: bash
- type: cli
name: git-helper
command: ./tools/git-helper/script.sh
description: Run git operations
readme: ./tools/git-helper/README.md
skills_dir: ./skills/
Fields Reference
metadata
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Unique identifier (alphanumeric + hyphens) |
description | string | No | Human-readable description |
version | string | No | Semantic version |
labels | map | No | Key-value labels for filtering |
spec.model
| Field | Type | Required | Description |
|---|---|---|---|
provider | string | Yes | openrouter, openai, anthropic, or ollama |
name | string | Yes | Model name/identifier |
temperature | float | No | Sampling temperature (0-2, default 0.7) |
max_input_tokens | int | No | Cap input tokens (for cost control) |
max_output_tokens | int | No | Max response tokens |
base_url | string | No | Override provider’s base URL |
Prompt Files
| Field | Points To | Purpose |
|---|---|---|
soul | Markdown file | “Who the agent IS” — identity and personality |
system_prompt | Markdown file | “What the agent DOES” — core system prompt |
instructions | Markdown file | Additional runtime instructions |
SOUL.md example:
Communication style rules:
- Be concise and concrete; prefer bullet points over paragraphs
- Ask one clarifying question when requirements are ambiguous
- Match the user's tone; only use emojis if the user uses them first
spec.session
| Field | Type | Default | Description |
|---|---|---|---|
on_disconnect | string | pause | continue or pause |
max_tool_iterations | int | 10 | Max tool call iterations per request |
llm_timeout_seconds | int | 300 | Timeout for LLM requests in seconds |
ttl_hours | int | (global) | Per-agent session TTL override |
compaction | string | (global) | Per-agent compaction override |
spec.session.context
Controls how conversation history and tool results are truncated to fit within the model’s context window.
| Field | Type | Default | Description |
|---|---|---|---|
max_history_tokens | int | 20000 | Max tokens for conversation history (0 = no cap) |
max_tool_result_tokens | int | 8000 | Max tokens per tool result |
tool_result_truncation | string | head | head, tail, or both |
tool_result_keep_first | int | 2 | First N tool results kept visible |
tool_result_keep_last | int | 5 | Last M tool results kept visible |
spec.tools
See Tools and Policies for full details.
spec.access
See Group Chat for full details.
spec.memory
See Memory for full details.
Versioning
The format uses API versions:
| Version | Stability |
|---|---|
duragent/v1alpha1 | Alpha (current, breaking changes allowed) |
duragent/v1beta1 | Beta (mostly stable) |
duragent/v1 | Stable (future) |
Examples
Q&A Bot
apiVersion: duragent/v1alpha1
kind: Agent
metadata:
name: qa-bot
spec:
model:
provider: openrouter
name: anthropic/claude-sonnet-4
system_prompt: ./SYSTEM_PROMPT.md
Code Review Agent
apiVersion: duragent/v1alpha1
kind: Agent
metadata:
name: code-reviewer
spec:
model:
provider: openrouter
name: anthropic/claude-sonnet-4
temperature: 0.3
system_prompt: ./SYSTEM_PROMPT.md
instructions: ./INSTRUCTIONS.md
session:
on_disconnect: pause
tools:
- type: builtin
name: bash
- type: cli
name: git-diff
command: ./tools/git-diff.sh
description: Show git diff for review
Autonomous Supervisor
apiVersion: duragent/v1alpha1
kind: Agent
metadata:
name: code-supervisor
spec:
model:
provider: anthropic
name: claude-sonnet-4-20250514
system_prompt: ./SYSTEM_PROMPT.md
session:
on_disconnect: continue
max_tool_iterations: 50
tools:
- type: builtin
name: bash
Tools and Policies
Duragent agents can use tools to interact with the outside world. A policy system controls which tools are allowed, with optional human-in-the-loop approval.
Tool Types
| Type | Description | Best For |
|---|---|---|
| Built-in | Bundled with Duragent | Core operations (e.g., bash) |
| CLI | Custom scripts with optional README | Simple extensions, any language |
| MCP | Model Context Protocol servers | Complex integrations (planned) |
Configuration
# agent.yaml
spec:
tools:
# Built-in tool
- type: builtin
name: bash
# CLI tool
- type: cli
name: code-search
command: ./tools/code-search.sh
description: Search codebase for patterns
readme: ./tools/code-search/README.md
Built-in Tools
| Name | Description |
|---|---|
bash | Execute shell commands in sandbox |
web_search | Search the web (requires BRAVE_API_KEY) |
web_fetch | Fetch a URL and convert to Markdown |
schedule_task | Create a scheduled task |
list_schedules | List active schedules |
cancel_schedule | Cancel a schedule by ID |
Memory tools (recall, remember, reflect, update_world) are automatically registered when memory is configured. See Memory.
web_search
Searches the web using the Brave Search API.
- Parameters:
query(string, required),count(integer, 1–20, default 5) - Requires:
BRAVE_API_KEYenvironment variable. If not set, the tool is silently skipped when registering. - Timeout: 30 seconds
spec:
tools:
- type: builtin
name: web_search
web_fetch
Fetches a web page and converts HTML to Markdown.
- Parameters:
url(string, required —httpandhttpsonly) - Download limit: 1 MB response body
- Output limit: 50 KB sent to the LLM (truncated with notice if larger)
- Timeout: 30 seconds
- No API key required — always available as a built-in tool
spec:
tools:
- type: builtin
name: web_fetch
CLI Tools
CLI tools are scripts or binaries that the agent can call. They’re more token-efficient than MCP because the agent reads the README only when it needs the tool (no upfront schema exchange).
| Field | Required | Description |
|---|---|---|
type | Yes | cli |
name | Yes | Tool identifier |
command | Yes | Script path (relative to agent directory) |
description | No | Short description shown to LLM |
readme | No | Path to README (loaded on demand) |
Tool Policy
The policy system controls which tools agents can execute. It supports three modes with a deny list safety net.
File Layout
agents/my-agent/
agent.yaml
policy.yaml # Base policy (version controlled)
policy.local.yaml # User overrides (gitignored, auto-created)
Policy Modes
| Mode | Deny List | Allow List | Unknown Commands |
|---|---|---|---|
dangerous | Blocks (always) | Ignored | Allowed |
ask | Blocks (always) | Auto-approved | Requires human approval |
restrict | Blocks (always) | Allowed | Denied |
The deny list is always checked first regardless of mode — it acts as an air-gap safety mechanism.
Example Policy
# policy.yaml
apiVersion: duragent/v1alpha1
kind: Policy
mode: ask
deny:
- "bash:rm -rf /*"
- "bash:*sudo*"
- "*:*password*"
allow:
- "bash:cargo *"
- "bash:git *"
- "mcp:github:*"
notify:
enabled: true
patterns:
- "bash:git push*"
deliveries:
- type: log
- type: webhook
url: https://hooks.slack.com/services/...
Pattern Format
Patterns use tool_type:pattern with glob-style matching:
| Pattern | Matches |
|---|---|
bash:cargo * | Bash commands starting with “cargo” |
mcp:github:* | Any tool from github MCP server |
*:*secret* | “secret” in any tool type |
Approval Flow (Ask Mode)
When a command isn’t in the allow or deny list:
- Agent requests tool invocation
- User sees an approval prompt
- User chooses: Allow Once, Allow Always, or Deny
- “Allow Always” saves the pattern to
policy.local.yamlfor future auto-approval
Merge Behavior
When both policy.yaml and policy.local.yaml exist:
| Field | Strategy |
|---|---|
mode | Local overrides base (unless dangerous) |
deny | Lists merged (union) |
allow | Lists merged (union) |
notify | Lists merged |
Default Behavior
When no policy files exist, the default is dangerous mode with no filtering — matching pre-policy behavior.
Notifications
You can configure notifications for specific tool patterns:
notify:
enabled: true
patterns:
- "bash:rm *"
- "bash:git push*"
deliveries:
- type: log
- type: webhook
url: https://hooks.slack.com/services/...
Notifications don’t block execution — they fire after the command runs.
Memory
Duragent provides a file-first memory system using plain markdown files. Agents decide when to read and write memory using four built-in tools.
Design
- Markdown-first — All memory is plain text files you can read, edit, and version
- Tools-only — No automatic injection; the agent decides when to use memory
- Curation over automation — Agents promote important learnings manually
Directory Structure
{workspace}/
├── memory/
│ └── world/ # Shared facts (all agents see)
│ ├── people.md
│ ├── systems.md
│ └── {topic}.md
└── agents/
└── {agent-name}/
├── agent.yaml
└── memory/ # Agent-specific
├── MEMORY.md # Curated long-term memory
└── daily/
└── YYYY-MM-DD.md # Daily experiences (append-only)
| Path | Owner | Purpose |
|---|---|---|
memory/world/*.md | Shared | Objective facts about the world |
agents/{name}/memory/MEMORY.md | Agent | Curated learnings |
agents/{name}/memory/daily/*.md | Agent | Daily experience log |
Enabling Memory
Add a memory section to your agent spec:
# agent.yaml
spec:
memory:
backend: filesystem
When the memory section is present, all four memory tools are automatically registered. When omitted, no memory tools are available.
Memory Tools
| Tool | Action | When to Use |
|---|---|---|
recall | Read memory context | Start of conversation, when context needed |
remember | Append to daily log | After learning something |
reflect | Rewrite MEMORY.md | End of session, consolidate learnings |
update_world | Write world knowledge topic | New shared fact discovered |
recall
Load memory context — world knowledge, agent memory, and recent daily logs.
- Parameters:
days(integer, default: 3) - Returns: Concatenated content from world files, MEMORY.md, and the last N days of daily logs
remember
Append an experience to today’s daily log.
- Parameters:
content(string, required) - Behavior: Appends a timestamped entry to
memory/daily/YYYY-MM-DD.md
reflect
Rewrite the agent’s long-term memory.
- Parameters:
content(string, optional) - Behavior: When
contentis provided, atomic write tomemory/MEMORY.md. When omitted, reads and returns the currentmemory/MEMORY.md.
update_world
Write shared world knowledge for a topic.
- Parameters:
topic(string, required),content(string, required) - Behavior: Atomic write to
world/{topic}.md(replaces existing content)
Configuration
Global (duragent.yaml)
workspace: .duragent
# Optional: override world memory location
# world_memory:
# path: custom/memory/world
The world memory directory defaults to {workspace}/memory/world.
Per-Agent (agent.yaml)
spec:
memory:
backend: filesystem # Enables all 4 memory tools
Directives
Directives are *.md files that are injected into the system prompt. They’re loaded from two directories:
{workspace}/directives/— workspace-level, shared across all agents{agent_dir}/directives/— agent-level, per-agent
When any agent has memory configured, a default memory.md directive is auto-created at {workspace}/directives/memory.md with instructions for using memory tools.
Growth Management
| File Type | Expected Size | Management |
|---|---|---|
world/*.md | 1-10 KB each | Agent rewrites during update_world |
MEMORY.md | 2-10 KB | Agent rewrites during reflect |
daily/*.md | 0.5-2 KB/day | Old files naturally become irrelevant |
For searching older memories, you can use external tools like fmd — a fast BM25F markdown search tool.
Scheduling
Agents can create time-based triggers to proactively send messages or execute tasks. Schedules persist across restarts and deliver results via any configured gateway.
Overview
Scheduling lets agents handle use cases like:
- “Remind me to check deployment status tomorrow at 9am”
- “Send me a daily standup prompt on weekdays”
- “Follow up on this issue in 2 hours”
Schedule Types
| Type | Description | Example |
|---|---|---|
One-shot (at) | Fire once at a specific time | 2026-02-01T09:00:00Z |
Interval (every) | Repeat every N seconds | 3600 (hourly) |
| Cron | Standard cron expression | 0 9 * * MON-FRI (9am weekdays) |
Schedule Tools
Three built-in tools are available for scheduling:
schedule_task
Create a new schedule. The agent specifies the timing, destination, and payload.
list_schedules
List all active schedules for the current agent.
cancel_schedule
Cancel an active schedule by its ID.
Payload Types
| Type | Description |
|---|---|
message | Send text directly (no LLM call) |
task | Execute with agent tools, summarize results |
Example Scenarios
One-Time Reminder
- User: “Remind me to review the PR in 2 hours”
- Agent calls
schedule_taskwithat: "2026-01-30T16:00:00Z" - Two hours later, user receives: “Reminder: review the PR”
Recurring Prompt
- User: “Send me a daily standup prompt at 9am on weekdays”
- Agent calls
schedule_taskwithcron: "0 9 * * MON-FRI" - Every weekday at 9am, user receives the prompt
- User replies, and the conversation continues naturally
Session Continuity
Scheduled messages inject into the active session for a (chat_id, agent) pair if one exists. If not, a new session is created. This means scheduled messages appear in the same conversation as user messages — enabling natural follow-up.
Storage
Schedules are persisted as YAML files:
.duragent/
├── schedules/
│ ├── sched_01HQXYZ.yaml # Schedule definition
│ └── runs/
│ └── sched_01HQXYZ.jsonl # Run history
Schedule File Format
id: reminder-abc123
agent: my-assistant
created_by_session: session_xyz
destination:
gateway: telegram
chat_id: "123456789"
schedule:
at: # one-shot
at: "2026-01-30T16:00:00Z"
# or:
# cron: # recurring
# expr: "0 9 * * MON-FRI"
payload:
message:
message: "Reminder: review the PR"
Retry
Schedules support optional retry with exponential backoff and jitter for transient failures (e.g., LLM provider 503).
Gateway Plugins
Gateway plugins enable Duragent to communicate with messaging platforms. They run as separate processes and communicate via the Gateway Protocol (JSON Lines over stdio).
Configuration
Gateway plugins are configured in duragent.yaml:
gateways:
# Built-in Telegram gateway
telegram:
enabled: true
bot_token: ${TELEGRAM_BOT_TOKEN}
# External gateways (subprocess plugins)
external:
- name: discord
command: /usr/local/bin/duragent-discord
args: ["--verbose"]
env:
DISCORD_BOT_TOKEN: ${DISCORD_BOT_TOKEN}
restart: on_failure
- name: custom-gateway
command: ./my-gateway
restart: always
External Gateway Fields
| Field | Type | Default | Description |
|---|---|---|---|
name | string | required | Gateway identifier |
command | string | required | Path to gateway binary |
args | array | [] | Command arguments |
env | map | {} | Environment variables |
restart | enum | on_failure | always, on_failure, or never |
Agent Routing
Routing rules determine which agent handles messages from each gateway. Rules are global and evaluated in order:
routes:
- match:
gateway: telegram
sender_id: "123456789"
agent: personal-assistant
- match:
gateway: telegram
chat_type: group
agent: group-moderator
- match: {} # Catch-all
agent: default-assistant
Match Conditions
| Field | Description | Examples |
|---|---|---|
gateway | Gateway name | telegram, discord |
chat_type | Conversation type | dm, group, channel |
chat_id | Specific chat ID | -1001234567890 |
sender_id | Specific user ID | 123456789 |
All conditions in a rule must match (AND logic). First match wins. An empty match: {} acts as a catch-all.
Process Management
Duragent manages plugin lifecycle:
- Startup — Plugins are spawned when the server starts
- Health checks — Periodic pings to verify plugin is responsive
- Restart — Configurable restart policy on crash
- Shutdown — Graceful shutdown signal, then force kill
- Orphan prevention — On Linux, plugins die when Duragent dies (via
prctl)
Writing a Custom Gateway
Custom gateways implement the Gateway Protocol — JSON Lines over stdin/stdout. You can write them in any language.
Protocol Messages
Events (gateway to Duragent):
| Event | Purpose |
|---|---|
ready | Gateway initialized |
message_received | Incoming user message |
callback_query | Inline keyboard button pressed |
command_ok | Command succeeded |
command_error | Command failed |
pong | Response to health check ping |
error | Gateway-level error |
auth_required | Authentication needed (e.g. QR code) |
auth_success | Authentication succeeded |
shutdown | Gateway terminating |
Commands (Duragent to gateway):
| Command | Purpose |
|---|---|
send_message | Send text to a chat |
send_media | Send media (image, video, audio, document) |
send_typing | Show typing indicator |
edit_message | Edit a previously sent message |
delete_message | Delete a message |
answer_callback_query | Respond to inline keyboard button press |
ping | Health check |
shutdown | Graceful termination request |
Message Metadata
Gateways provide metadata with incoming messages:
chat_id— Platform-specific chat identifierchat_type—dm,group, orchannelsender_id— User identifiermentions_bot— Whether the bot was @mentionedreply_to_bot— Whether this is a reply to the bot’s message
See the duragent-gateway-protocol crate for the full type definitions.
Group Chat
Duragent provides layered access control for DMs and groups, with mention gating, message queuing, and per-sender policies.
Access Policies
DM Policies
Control who can send direct messages to your agent:
| Policy | Behavior | Use Case |
|---|---|---|
open | Accept all DMs (default) | Public bot |
disabled | Reject all DMs | Group-only bot |
allowlist | Only listed user IDs | Private bot |
spec:
access:
dm:
policy: allowlist
allowlist: ["12345", "67890"]
Allowlists support trailing * wildcards (e.g., user_*).
Group Policies
Control which groups the agent participates in:
| Policy | Behavior | Use Case |
|---|---|---|
open | Participate in any group (default) | Public bot |
disabled | Ignore all group messages | DM-only bot |
allowlist | Only listed groups | Controlled deployment |
spec:
access:
groups:
policy: allowlist
allowlist: ["telegram:-100123456", "discord:987654321"]
Group allowlists use {channel}:{chat_id} composite format. Wildcards are supported (e.g., telegram:* for all Telegram groups).
Mention Gating
In groups, agents use mention gating by default — they only respond when triggered.
| Mode | Behavior |
|---|---|
mention (default) | Only respond when @mentioned or replied to |
always | Respond to every allowed message |
spec:
access:
groups:
activation: mention
Mention detection supports:
- Explicit
@bot_usernamein message text - Reply to bot’s previous message
Per-Sender Policies
Within groups, you can assign different dispositions to different senders:
| Disposition | LLM Sees It? | Triggers Response? | Use Case |
|---|---|---|---|
allow | Yes | Yes | Normal interaction |
passive | Yes (future turns) | No | Context without triggering |
silent | No | No | Audit trail only |
block | No | No | Spam, bad actors |
spec:
access:
groups:
sender_default: silent
sender_overrides:
"67890": allow
"99999": block
"admin_*": allow # Wildcard support
Resolution order: exact sender ID match, then wildcard pattern match, then sender_default.
Context Buffer
When not mentioned in mention mode, messages from allow senders are buffered. When the agent is triggered, recent context is injected so it understands the conversation.
spec:
access:
groups:
context_buffer:
mode: silent # silent (default) | passive
max_messages: 100 # Max buffered messages
max_age_hours: 24 # Max age for buffer messages
| Mode | Storage | Survives Crash? | Token Cost |
|---|---|---|---|
silent | In-memory buffer | No | Low (only on trigger) |
passive | Conversation history | Yes | Accumulates over time |
Message Queue
When messages arrive while the agent is processing, the queue handles them:
spec:
access:
groups:
queue:
mode: batch # batch (default) | sequential | drop
max_pending: 10
overflow: drop_old # drop_old | drop_new | reject
debounce:
enabled: true
window_ms: 1500
Queue Modes
| Mode | Behavior | Use Case |
|---|---|---|
batch | Combine all pending into one request | Rapid message senders |
sequential | Process one at a time until empty | Preserve individual context |
drop | Discard pending messages | Simple, predictable |
Overflow Strategies
| Strategy | Behavior |
|---|---|
drop_old | Evict oldest message to make room |
drop_new | Silently discard the new message |
reject | Discard and send reject_message back |
Debouncing
Per-sender debouncing batches rapid messages before they enter the queue (e.g., user sends 3 messages in 2 seconds — they get combined into one). The first message from an idle sender bypasses debouncing for zero latency.
Full Example
spec:
access:
dm:
policy: allowlist
allowlist: ["12345"]
groups:
policy: allowlist
allowlist: ["telegram:-100123456"]
sender_default: passive
sender_overrides:
"67890": allow
"99999": block
activation: mention
context_buffer:
mode: silent
max_messages: 50
max_age_hours: 12
queue:
mode: sequential
max_pending: 5
overflow: reject
reject_message: "I'm busy, please wait."
Cost Impact
With mention gating, 1000 group messages/day at a 5% mention rate results in only ~50 LLM calls — a 95% reduction compared to responding to every message.
Writing Skills
Skills are reusable behaviors that teach agents how to accomplish tasks. Unlike tools (which provide capabilities), skills provide instructions and patterns.
Skills vs Tools
| Concept | Purpose | Format |
|---|---|---|
| Tool | A capability the agent can invoke | CLI command or built-in (MCP planned) |
| Skill | Instructions for how to accomplish a task | SKILL.md with steps, triggers, examples |
Skills may use tools, but they’re primarily about teaching patterns.
Directory Structure
skills/
└── task-extraction/
├── SKILL.md # Required: skill definition
├── scripts/
│ └── extract.py # Optional: executable scripts
├── references/
│ └── examples.md # Optional: loaded into context
└── assets/
└── template.txt # Optional: templates
SKILL.md Format
Duragent adopts the Agent Skills open standard, ensuring portability across ecosystems.
Structure
- YAML frontmatter between
---delimiters - Required fields:
name,description - Optional fields:
allowed-tools,metadata - Body content: instructions, examples, output format
Example
---
name: task-extraction
description: Extract actionable tasks from a message or conversation
allowed-tools: calculator
metadata:
version: "1.0.0"
author: Duragent
---
## Instructions
1. Find explicit action items and implied commitments.
2. Present tasks in a consistent JSON shape.
## Output Format
Return tasks as JSON:
\`\`\`json
{
"tasks": [
{
"description": "Review PR #123",
"assignee": "alice",
"priority": "high"
}
]
}
\`\`\`
Naming Rules
- Lowercase letters, digits, and hyphens only
- Maximum 64 characters
- Must match the directory name
Configuring Skills
Point your agent to a skills directory:
# agent.yaml
spec:
skills_dir: ./skills/
Duragent scans the directory for subdirectories containing SKILL.md files. The name field in the SKILL.md frontmatter is required and must match the directory name.
Best Practices
- Keep skills focused — one skill per task type
- Include examples — put reference material in
references/for the agent to load - Use allowed-tools — declare which tools the skill expects to use
- Test with real conversations — verify the skill produces the expected output
Simple Mode (Self-Hosted)
For self-hosted power users, Duragent runs as a single binary with all state stored as files. Zero external dependencies.
Architecture
duragent serve
├── Core Gateways (CLI, HTTP, SSE)
├── Gateway Protocol → [Telegram] [Discord] (plugins)
└── File-Based State
└── .duragent/
├── agents/my-agent/
│ ├── agent.yaml
│ ├── SOUL.md
│ ├── SYSTEM_PROMPT.md
│ ├── INSTRUCTIONS.md
│ ├── policy.yaml
│ ├── skills/
│ └── memory/
├── sessions/
│ └── session_abc/
│ ├── events.jsonl
│ └── state.yaml
├── schedules/
└── memory/world/
Setup
1. Initialize a Workspace
duragent init
# Follow the interactive setup
2. Set Up Your API Key and Start the Server
duragent login anthropic # or: export OPENROUTER_API_KEY=your-key
duragent serve
3. Chat
duragent chat --agent <YOUR_AGENT_NAME>
Properties
- Zero external dependencies — single binary, no database, no Docker
- All state is files — git-friendly, inspectable, editable
- Memory is markdown — human-readable, easy to export
- Core gateways built-in — CLI, HTTP, SSE
- Platform gateways via plugins — optional, separate binaries
Storage Configuration
By default, all state lives under .duragent/. You can customize paths:
# duragent.yaml
workspace: .duragent
services:
session:
path: .duragent/sessions
world_memory:
path: .duragent/memory/world
File Formats
| Format | Use Case |
|---|---|
| JSONL | Event streams (append-only, fast writes) |
| YAML | Structured state (snapshots, schedules) |
| Markdown | Prose content (prompts, memory) |
All files are human-readable and designed for version control.
Gateway Setup
This guide covers setting up Telegram and Discord gateways for Duragent.
Telegram
1. Create a Bot
- Message @BotFather on Telegram
- Send
/newbotand follow the prompts - Copy the bot token
2. Install the Gateway
cargo install --git https://github.com/giosakti/duragent.git duragent-gateway-telegram
3. Configure
# duragent.yaml
gateways:
external:
- name: telegram
command: duragent-gateway-telegram
env:
TELEGRAM_BOT_TOKEN: ${TELEGRAM_BOT_TOKEN}
restart: on_failure
routes:
- match:
gateway: telegram
agent: my-assistant
Note: If you compiled Duragent with
--features gateway-telegram, you can use the built-in config instead:gateways: telegram: enabled: true bot_token: ${TELEGRAM_BOT_TOKEN}
4. Start
export TELEGRAM_BOT_TOKEN=your-token
export OPENROUTER_API_KEY=your-key
duragent serve
Your bot is now live. Send it a message on Telegram.
Telegram Features
- Long polling (no webhook setup needed)
- Inline keyboard buttons for tool approval
- Typing indicator while processing
Discord
1. Create a Bot
- Go to Discord Developer Portal
- Create a New Application
- Go to Bot tab, click Add Bot
- Copy the bot token
- Under Privileged Gateway Intents, enable Message Content Intent
- Go to OAuth2 > URL Generator, select
botscope withSend MessagesandRead Message Historypermissions - Use the generated URL to invite the bot to your server
2. Install the Gateway
cargo install --git https://github.com/giosakti/duragent.git duragent-gateway-discord
3. Configure
# duragent.yaml
gateways:
external:
- name: discord
command: duragent-gateway-discord
env:
DISCORD_BOT_TOKEN: ${DISCORD_BOT_TOKEN}
restart: on_failure
routes:
- match:
gateway: discord
agent: my-assistant
Note: If you compiled Duragent with
--features gateway-discord, you can use the built-in config instead:gateways: discord: enabled: true bot_token: ${DISCORD_BOT_TOKEN}
4. Start
export DISCORD_BOT_TOKEN=your-token
export OPENROUTER_API_KEY=your-key
duragent serve
Discord Features
- DM and server channel support
- Button components for tool approval
- 2000-character message chunking
- Reply threading
- Typing indicator while processing
Multiple Gateways
You can run multiple gateways simultaneously with different routing:
gateways:
telegram:
enabled: true
bot_token: ${TELEGRAM_BOT_TOKEN}
external:
- name: discord
command: duragent-gateway-discord
env:
DISCORD_BOT_TOKEN: ${DISCORD_BOT_TOKEN}
routes:
- match:
gateway: telegram
chat_type: group
agent: group-assistant
- match:
gateway: discord
agent: discord-assistant
- match: {}
agent: default-assistant
Custom Gateways
You can write custom gateways in any language. They communicate with Duragent via JSON Lines over stdio. See Gateway Plugins for the protocol specification.
gateways:
external:
- name: my-custom-gateway
command: ./my-gateway-binary
restart: always
Production Deployment
This page is a work in progress.
For production deployment guidance, see Simple Mode for the current single-instance setup.
CLI Commands
Setup
duragent init
Initialize a new Duragent workspace.
duragent init [path] [flags]
Flags:
--agent-name string Name for the starter agent
--provider string LLM provider (anthropic, openrouter, openai, ollama)
--model string Model name
--no-interactive Skip interactive prompts; use defaults
Example:
duragent init
duragent init --agent-name my-bot --provider anthropic
duragent login
Authenticate with an LLM provider via OAuth. Currently only anthropic is supported.
Credentials are stored at ~/.duragent/auth.json (mode 0600) and take precedence over the corresponding environment variable (e.g. ANTHROPIC_API_KEY). Tokens are automatically refreshed when they expire.
duragent login <provider>
Example:
duragent login anthropic
Server
duragent serve
Start the Duragent server.
duragent serve [flags]
Flags:
--host string Host to bind to (overrides config)
-p, --port int HTTP port (overrides config)
--agents-dir string Path to agents directory (overrides config)
-c, --config string Path to config file (default duragent.yaml)
--ephemeral <SECONDS> Auto-shutdown after N seconds with no active sessions
Example:
duragent serve
duragent serve --port 9090
duragent serve stop
Stop a running server.
duragent serve stop
duragent serve reload-agents
Reload agent configurations from disk without restarting the server.
duragent serve reload-agents
Sessions
duragent chat
Start an interactive chat session with an agent.
duragent chat [flags]
Flags:
-a, --agent string Agent name (required)
--agents-dir string Path to agents directory (overrides config)
-c, --config string Path to config file (default duragent.yaml)
-s, --server string Connect to a specific server URL
Examples:
duragent chat --agent my-assistant
duragent attach
Attach to an existing session (like tmux attach).
duragent attach [SESSION_ID] [flags]
Flags:
-l, --list List all attachable sessions
--agents-dir string Path to agents directory (overrides config)
-c, --config string Path to config file (default duragent.yaml)
-s, --server string Connect to a specific server URL
Examples:
duragent attach --list
duragent attach SESSION_ID
When attaching to a session with on_disconnect: continue, you’ll see any output that was buffered while you were away.
Interactive Commands
Within duragent chat, these commands are available:
| Command | Description |
|---|---|
/quit or /exit | End session |
Ctrl+D | Detach from session (EOF) |
Configuration
Duragent is configured via duragent.yaml (server-level) and agent.yaml (per-agent). This page covers server configuration; for agent configuration see Agent Format.
Environment Variable Interpolation
Configuration files support shell-style environment variable expansion:
| Syntax | Behavior |
|---|---|
${VAR} | Required — errors if not set |
${VAR:-default} | Optional — uses default if not set |
${VAR:-} | Optional — empty string if not set |
Environment Variables
Duragent reads certain environment variables directly at startup, independent of config file interpolation.
LLM Provider Keys
These are read directly from the environment — there is no YAML config equivalent. Anthropic also supports OAuth login via duragent login anthropic, which stores credentials at ~/.duragent/auth.json and takes precedence over the environment variable. See Authentication for details.
| Variable | Required | Description |
|---|---|---|
ANTHROPIC_API_KEY | No | API key for Anthropic (Claude). Not needed if using OAuth login. |
OPENAI_API_KEY | No | API key for OpenAI-compatible providers |
OPENROUTER_API_KEY | No | API key for OpenRouter |
At least one LLM provider must be configured for agents to function.
Tool Keys
These are read directly from the environment — there is no YAML config equivalent.
| Variable | Required | Description |
|---|---|---|
BRAVE_API_KEY | No | Brave Search API key. Enables the web_search built-in tool. |
Gateway Tokens
Gateway tokens are handled differently depending on how you run the gateway:
- In-process (compiled-in feature): the token comes from the
bot_tokenfield induragent.yaml, which supports${VAR}interpolation. - Standalone binary: the token is read directly from the environment variable below.
These are separate code paths — they do not conflict.
| Variable | Binary | Description |
|---|---|---|
DISCORD_BOT_TOKEN | duragent-discord | Discord bot token |
TELEGRAM_BOT_TOKEN | duragent-telegram | Telegram bot token |
Tip: When running gateways as external plugins (via
gateways.external[]), you can forward these through theenvfield induragent.yamlusing${VAR}interpolation instead of relying on the host environment.
duragent.yaml
Full Example
# Workspace root (default: .duragent)
# workspace: .duragent
# Server
server:
host: 0.0.0.0
port: 8080
request_timeout_seconds: 300
idle_timeout_seconds: 60
keep_alive_interval_seconds: 15
admin_token: ${ADMIN_TOKEN:-}
api_token: ${API_TOKEN:-}
# Agent directory (optional, defaults to {workspace}/agents)
# agents_dir: .duragent/agents
# Services
services:
session:
# path: .duragent/sessions
# World memory
world_memory:
# path: .duragent/memory/world
# Sessions
sessions:
ttl_hours: 168
compaction: discard
# Gateways
gateways:
telegram:
enabled: true
bot_token: ${TELEGRAM_BOT_TOKEN}
external:
- name: discord
command: /usr/local/bin/duragent-discord
args: ["--verbose"]
env:
DISCORD_BOT_TOKEN: ${DISCORD_BOT_TOKEN}
restart: on_failure
# Routes
routes:
- match:
gateway: telegram
sender_id: "123456789"
agent: personal-assistant
- match:
gateway: telegram
chat_type: group
agent: group-moderator
- match: {}
agent: default-assistant
# Sandbox
sandbox:
mode: trust
Fields Reference
Server
| Field | Type | Default | Description |
|---|---|---|---|
server.host | string | 127.0.0.1 | Bind address |
server.port | u16 | 8080 | HTTP port |
server.request_timeout_seconds | u64 | 300 | Non-streaming request timeout |
server.idle_timeout_seconds | u64 | 60 | SSE idle timeout |
server.keep_alive_interval_seconds | u64 | 15 | SSE keep-alive interval |
server.admin_token | string? | none | Admin API token |
server.api_token | string? | none | API token. If set, API endpoints require this token. If not set, only localhost requests are accepted. |
server.max_connections | usize | 1024 | Maximum concurrent connections |
Workspace
| Field | Type | Default | Description |
|---|---|---|---|
workspace | path? | .duragent | Workspace root directory |
agents_dir | path? | {workspace}/agents | Agent definitions directory |
services.session.path | path? | {workspace}/sessions | Session storage directory |
world_memory.path | path? | {workspace}/memory/world | Shared world memory directory |
Sessions
| Field | Type | Default | Description |
|---|---|---|---|
sessions.ttl_hours | u64 | 168 | Hours of inactivity before session expiry. 0 disables. |
sessions.compaction | enum | discard | discard, archive, or disabled |
Gateways
| Field | Type | Default | Description |
|---|---|---|---|
gateways.telegram.enabled | bool | true | Enable Telegram gateway (requires gateway-telegram feature) |
gateways.telegram.bot_token | string | required | Telegram bot token |
gateways.discord.enabled | bool | true | Enable Discord gateway (requires gateway-discord feature) |
gateways.discord.bot_token | string | required | Discord bot token |
gateways.external[].name | string | required | Gateway identifier |
gateways.external[].command | string | required | Path to gateway binary |
gateways.external[].args | array | [] | Command arguments |
gateways.external[].env | map | {} | Environment variables |
gateways.external[].restart | enum | on_failure | always, on_failure, or never |
Routes
| Field | Type | Description |
|---|---|---|
routes[].match | object | Match conditions (all must match, AND logic) |
routes[].agent | string | Agent to route to |
Match conditions:
| Field | Description |
|---|---|
gateway | Gateway name (telegram, discord) |
chat_type | dm, group, or channel |
chat_id | Specific chat ID |
sender_id | Specific user ID |
Routes are evaluated top-to-bottom; first match wins. An empty match: {} acts as a catch-all.
Sandbox
| Field | Type | Default | Description |
|---|---|---|---|
sandbox.mode | string | trust | trust (only supported mode; bubblewrap and docker are planned) |
Path Resolution
All relative paths are resolved relative to the config file directory, not the current working directory. When optional path fields are omitted, they default to subdirectories of the workspace.
Context Window Management
Context window settings are configured per-agent in agent.yaml under spec.session.context. See Agent Format > session.context for details.
Duragent automatically detects context window sizes from model names when max_input_tokens is not set. Supported model families include Claude, GPT-4/5, Gemini, Grok, DeepSeek, Qwen, Llama, and Mistral.
HTTP API
Duragent exposes an HTTP API for programmatic access to agents and sessions.
Authentication
API authentication depends on whether server.api_token is configured:
- Token configured: All
/api/v1/*routes require aBearertoken via theAuthorizationheader. - Token not configured: Only requests from localhost (
127.0.0.1,::1) are accepted.
Admin routes (/api/admin/v1/*) follow the same logic using server.admin_token.
Health endpoints (/livez, /readyz, /version) are always public.
curl -H "Authorization: Bearer YOUR_TOKEN" http://localhost:8080/api/v1/agents
Response Format
Success
Success responses return data directly as JSON:
{
"session_id": "session_abc123",
"agent": "my-assistant",
"status": "active"
}
Errors (RFC 7807)
Errors use RFC 7807 Problem Details with Content-Type: application/problem+json:
{
"type": "urn:duragent:problem:not-found",
"title": "Not Found",
"status": 404,
"detail": "Agent 'my-agent' not found"
}
Public API
Agents
GET /api/v1/agents # List loaded agents
GET /api/v1/agents/{name} # Get agent details (includes full spec)
Sessions
GET /api/v1/sessions # List all sessions
POST /api/v1/sessions # Create new session
GET /api/v1/sessions/{session_id} # Get session details
DELETE /api/v1/sessions/{session_id} # End session
GET /api/v1/sessions/{session_id}/messages # Get message history
POST /api/v1/sessions/{session_id}/messages # Send message
POST /api/v1/sessions/{session_id}/stream # SSE stream
POST /api/v1/sessions/{session_id}/approve # Approve tool execution
Health
GET /livez # Liveness check
GET /readyz # Readiness check
GET /version # Version info
Admin API
The Admin API requires authentication via admin_token in the server config.
POST /api/admin/v1/shutdown # Graceful server shutdown
POST /api/admin/v1/reload-agents # Reload agent configurations from disk
SSE Streaming
Send a message and stream the response token-by-token:
curl -N -X POST http://localhost:8080/api/v1/sessions/{session_id}/stream \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"content": "Your message here"}'
Event Types
token — Partial response token:
event: token
data: {"content": "Hello"}
tool_call — Tool invocation started:
event: tool_call
data: {"call_id": "call_abc123", "name": "bash", "arguments": "{\"command\": \"ls\"}"}
tool_result — Tool execution completed:
event: tool_result
data: {"call_id": "call_abc123", "content": "file1.txt\nfile2.txt"}
approval_required — Tool needs user approval:
event: approval_required
data: {"call_id": "call_abc123", "command": "npm install sqlite3"}
done — Stream completed:
event: done
data: {"usage": {"prompt_tokens": 10, "completion_tokens": 8, "total_tokens": 18}}
start — Stream initialized:
event: start
data: {}
cancelled — Stream was cancelled (e.g. client disconnected):
event: cancelled
data: {}
error — Error occurred:
event: error
data: {"message": "LLM request failed: ..."}
Examples
Create and Use a Session
# Create a session
curl -X POST http://localhost:8080/api/v1/sessions \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"agent": "my-assistant"}'
# Send a message
curl -X POST http://localhost:8080/api/v1/sessions/session_abc123/messages \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"content": "Hello, how are you?"}'
# Stream a response
curl -N -X POST http://localhost:8080/api/v1/sessions/session_abc123/stream \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"content": "Write a short poem"}'
Error Codes
| Code | HTTP Status | Description |
|---|---|---|
not_found | 404 | Resource not found |
invalid_request | 400 | Malformed request |
unauthorized | 401 | Missing or invalid auth |
forbidden | 403 | Insufficient permissions |
conflict | 409 | Resource conflict |
internal_error | 500 | Server error |
session_not_found | 404 | Session does not exist |
session_ended | 410 | Session has been ended |
agent_not_found | 404 | Agent does not exist |