This document describes Duragent's deployment modes, storage configuration, and gateway setup.
"For simple deployments, be self-contained with file-based state. For complex deployments, delegate state to external systems."
Duragent follows a stateless core with pluggable state philosophy. The same binary can run in two modes:
┌──────────────────────────────────────────────────────────────┐
│ duragent serve │
│ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ Core Gateways (built-in) │ │
│ │ CLI/TUI │ HTTP REST │ SSE │ │
│ └────────────────────────────────────────────────────────┘ │
│ │ │
│ Gateway Protocol │
│ (JSON over stdio) │
│ │ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ Platform Gateways (plugins) │ │
│ │ [Telegram] [Discord] [Slack] │ │
│ │ (subprocess) (subprocess) (subprocess) │ │
│ └────────────────────────────────────────────────────────┘ │
│ │ │
│ Agent Executor │
│ │ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ File-Based State │ │
│ │ │ │
│ │ ./.duragent/ │ │
│ │ ├── agents/my-agent/ │ │
│ │ │ ├── agent.yaml # Structured agent config │ │
│ │ │ ├── SYSTEM_PROMPT.md # Unstructured prompt │ │
│ │ │ └── INSTRUCTIONS.md # Unstructured instructions│ │
│ │ ├── memory/ │ │
│ │ │ ├── 2025-01-10.md # Yesterday │ │
│ │ │ └── 2025-01-11.md # Today │ │
│ │ ├── sessions/ │ │
│ │ │ └── session_abc/ │ │
│ │ │ ├── events.jsonl # Append-only event log │ │
│ │ │ └── state.yaml # Snapshot for fast resume │ │
│ │ └── artifacts/ │ │
│ │ └── generated_file.txt │ │
│ └────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
Key properties:
- Zero external dependencies (single binary)
- All state is files (git-friendly, inspectable, editable)
- Memory is markdown files (human-readable)
- Core gateways built-in (CLI, HTTP, SSE)
- Platform gateways via subprocess plugins
┌─────────────────────────────────────────────────────────────┐
│ External System │
│ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ Gateway Layer │ │
│ │ Telegram Webhook ─┬─► User Resolution │ │
│ │ Web Controller ─┤ (chat_id → user_id) │ │
│ │ API ─┘ │ │ │
│ │ ▼ │ │
│ │ Agent Config Lookup │ │
│ │ (user_id → agent spec) │ │
│ └────────────────────────────┬──────────────────────────┘ │
└───────────────────────────────┼─────────────────────────────┘
│ Agent Protocol API
▼
┌─────────────────────────────────────────────────────────────┐
│ Duragent Worker Pool (Stateless) │
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Worker │ │ Worker │ │ Worker │ (scale horizontally) │
│ │ 1 │ │ 2 │ │ N │ │
│ └────┬────┘ └────┬────┘ └────┬────┘ │
│ └───────────┴───────────┘ │
│ │ │
│ gateways.http.enabled: true (API only, no plugins) │
│ gateways.external: [] (App handles platform routing)│
│ services.*: external (state in external services) │
└───────────────────────────┬─────────────────────────────────┘
│
┌───────────────────┼───────────────────┐
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Memory │ │ Session │ │ Artifacts │
│ (PostgreSQL) │ │ (PostgreSQL) │ │ (S3) │
└──────────────┘ └──────────────┘ └──────────────┘
Key properties:
- Workers are stateless (can restart/scale anytime)
- State is in external services (PostgreSQL, S3)
- Platform gateways disabled (application handles routing)
- Agent configs passed per-request or cached
| Aspect | Simple Mode | Complex Mode |
|---|---|---|
| Dependencies | Zero (single binary) | External services |
| Scalability | Single instance | Horizontal workers |
| Data durability | Files (git-friendly) | Database (ACID) |
| Debugging | Read files directly | Query services |
| Migration | Start simple → upgrade | Full control |
Precedents:
- Files → External backends — Same runtime, different state backend
- Ollama — Local by default, can connect to external
- nginx — Config files, stateless process
Duragent supports two state modes: file-based (default) and external backends.
# duragent.yaml - Simple Mode
data_dir: ./.duragent/
services:
session:
backend: filesystem
path: ./.duragent/sessions/
memory:
backend: filesystem
path: ./.duragent/memory/
artifacts:
backend: filesystem
path: ./.duragent/artifacts/Session format (file mode):
.duragent/sessions/{session_id}/
├── events.jsonl # Append-only event log
├── state.yaml # Periodic snapshot (for fast resume)
└── memory/
└── working.md # Agent's working notes
Memory format (file mode):
# 2025-12-15
## Decisions Made
- Chose Telegram as primary interface
- Named the agent "Socrates"
## Preferences Learned
- User prefers bullet points
- Timezone: Asia/Jakarta
## Facts Stored
- User's blog: example.com
- Current project: duragent# duragent.yaml - Complex Mode
services:
memory:
backend: postgres
url: ${DATABASE_URL}
session:
backend: postgres
url: ${DATABASE_URL}
artifacts:
backend: s3
bucket: my-artifacts
region: us-east-1Duragent keeps state concerns separate so deployments can mix-and-match backends (e.g., Postgres for sessions, File-based for memory, S3 for artifacts).
In code, this maps to three small traits instead of a single storage backend:
#[async_trait]
pub trait MemoryStore: Send + Sync {
async fn put(&self, agent_id: &str, entry: MemoryEntry) -> Result<()>;
async fn query(&self, agent_id: &str, q: MemoryQuery) -> Result<Vec<MemoryEntry>>;
async fn export(&self, agent_id: &str) -> Result<Box<dyn AsyncRead + Send>>; // portable export
}
#[async_trait]
pub trait SessionStore: Send + Sync {
// Sessions are append-only event logs (better for durability and concurrency).
async fn append(&self, session_id: &str, event: SessionEvent) -> Result<()>;
async fn read(&self, session_id: &str, opts: SessionReadOptions) -> Result<Vec<SessionEvent>>;
async fn delete(&self, session_id: &str) -> Result<()>;
}
#[async_trait]
pub trait ArtifactStore: Send + Sync {
async fn put(&self, agent_id: &str, name: &str, data: Box<dyn AsyncRead + Send>, meta: ArtifactMeta) -> Result<ArtifactRef>;
async fn get(&self, artifact_ref: &ArtifactRef) -> Result<Box<dyn AsyncRead + Send>>;
async fn list(&self, agent_id: &str, prefix: &str, limit: usize) -> Result<Vec<ArtifactRef>>;
}
pub struct ArtifactRef {
pub id: String, // stable identifier (or path for filesystem backends)
pub name: String,
pub url: Option<String>, // optional (S3/GCS); None for local filesystem
pub meta: ArtifactMeta,
}Users can start with file mode and upgrade to external mode without changing agent definitions:
# Start simple (filesystem)
services:
memory: { backend: filesystem, path: ./.duragent/memory/ }
session: { backend: filesystem, path: ./.duragent/sessions/ }
artifact: { backend: filesystem, path: ./.duragent/artifacts/ }
# Later, upgrade (example: postgres memory)
services:
memory: { backend: postgres, url: ${DATABASE_URL} }Duragent separates core gateways (built-in protocols) from platform gateways (plugins).
Always available, built into Duragent:
| Gateway | Use Case |
|---|---|
| CLI/TUI | Local development, duragent chat, duragent attach |
| HTTP REST | Programmatic access, webhooks, Admin API |
| SSE | LLM token streaming |
Platform-specific integrations run as separate subprocess plugins communicating via the Gateway Protocol (JSON over stdio):
# duragent.yaml
gateways:
# Core gateways (always available)
http:
enabled: true
port: 8080
cli:
enabled: true
# Platform gateways (subprocess plugins)
external:
- name: telegram
command: duragent-gateway-telegram
config:
bot_token: ${TELEGRAM_BOT_TOKEN}
- name: discord
command: duragent-gateway-discord
config:
token: ${DISCORD_TOKEN}For self-hosted power users, platform gateways run as managed subprocesses:
# duragent.yaml - Simple Mode
gateways:
http:
enabled: true
port: 8080
cli:
enabled: true
external:
- name: telegram
command: duragent-gateway-telegram
config:
bot_token: ${TELEGRAM_BOT_TOKEN}How it works:
- Duragent starts the gateway plugin subprocess
- Plugin receives message from Telegram
- Plugin sends message to Duragent via Gateway Protocol (JSON over stdio)
- Duragent routes to the configured agent
- Agent processes, returns response via Gateway Protocol
- Plugin sends response back to Telegram
For SaaS deployments, the application owns the gateway:
# duragent.yaml - Complex Mode
gateways:
http:
enabled: true
port: 8080
cli:
enabled: false
external: [] # Application handles platform routingHow it works:
- Application (e.g., Rails) receives webhook from Telegram
- Application identifies user from
chat_id - Application looks up user's agent configuration
- Application calls Duragent worker via HTTP REST API
- Application streams response via SSE
- Application sends response back to user
This separation allows:
- Application to handle authentication, billing, analytics
- Duragent workers to remain stateless and scalable
- Different gateway implementations per deployment
Sessions follow tmux-like semantics: disconnection does not stop the session.
| Mode | Behavior | Use Case |
|---|---|---|
continue |
Agent keeps executing, buffers output | Async workflows, supervisor agents |
pause |
Agent pauses at next safe point, waits for reconnect | Interactive chat |
# duragent.yaml - Global defaults
session:
on_disconnect: continue # or: pause
max_background_runtime: 30m # Safety limit for background execution
output_buffer_size: 1000 # Max events to buffer while disconnected# agent.yaml - Per-agent override
session:
on_disconnect: pause # Interactive agent pauses on disconnect| State | Description |
|---|---|
active |
Running, client attached |
running |
Running, client disconnected (continue mode) |
paused |
Paused, waiting for reconnect (pause mode) |
ended |
Completed or explicitly ended |
For supervisor agents that run autonomously:
# agents/code-supervisor/agent.yaml
name: code-supervisor
description: Completes coding tasks autonomously
session:
on_disconnect: continue
max_background_runtime: 2h
model:
provider: anthropic
name: claude-sonnet-4-20250514
tools:
builtin:
- claude_code_exec
- notify_userUser flow:
User: "Implement JWT auth, create PR when done, notify me on Slack"
│
[user disconnects]
│
▼
Agent: [continues working for 45 minutes]
Agent: [creates PR #123]
Agent: [sends Slack notification]
│
... later ...
│
User reconnects → sees full history and results
# 1. Create directory structure
mkdir -p ./.duragent/agents/my-assistant
# 2. Create system prompt
cat > ./.duragent/agents/my-assistant/SYSTEM_PROMPT.md << 'EOF'
You are a helpful assistant.
EOF
# 3. Create agent definition
cat > ./.duragent/agents/my-assistant/agent.yaml << 'EOF'
apiVersion: duragent/v1alpha1
kind: Agent
metadata:
name: my-assistant
spec:
model:
provider: openrouter
name: anthropic/claude-sonnet-4
system_prompt: ./SYSTEM_PROMPT.md
session:
on_disconnect: pause # Interactive agent
EOF
# 4. Run Duragent
export OPENROUTER_API_KEY=your-key
duragent serve --agents-dir ./.duragent/agents/
# 5. Chat via CLI
duragent chat --agent my-assistant# 1. Install Telegram gateway plugin
cargo install duragent-gateway-telegram
# 2. Create config
cat > duragent.yaml << 'EOF'
gateways:
http:
enabled: true
port: 8080
external:
- name: telegram
command: duragent-gateway-telegram
config:
bot_token: ${TELEGRAM_BOT_TOKEN}
agents:
static:
path: ./.duragent/agents
watch: true
EOF
# 3. Run
export TELEGRAM_BOT_TOKEN=your-token
export OPENROUTER_API_KEY=your-key
duragent serve# duragent.yaml
server:
host: 0.0.0.0
port: 8080
gateways:
http:
enabled: true
port: 8080
cli:
enabled: false
external: [] # Application handles platform routing
services:
session:
backend: postgres
url: ${DATABASE_URL}
memory:
backend: postgres
url: ${DATABASE_URL}
artifacts:
backend: s3
bucket: ${S3_BUCKET}
region: ${AWS_REGION}
session:
on_disconnect: continue
max_background_runtime: 1h
agents:
dynamic:
path: ./.duragent/sys/dyn_agents
lazy_load: true
cache_size: 10000
admin:
enabled: true
token: ${DURAGENT_ADMIN_TOKEN}# Run with Docker
docker run -d \
-e DATABASE_URL=$DATABASE_URL \
-e S3_BUCKET=$S3_BUCKET \
-e AWS_REGION=$AWS_REGION \
-e DURAGENT_ADMIN_TOKEN=$DURAGENT_ADMIN_TOKEN \
-p 8080:8080 \
duragent/duragent:latest serveservices:
session: { backend: filesystem }
memory: { backend: filesystem }
artifacts: { backend: filesystem }services:
session: { backend: postgres, url: ${DATABASE_URL} }
memory: { backend: filesystem } # Keep local for easy editing
artifacts: { backend: s3, bucket: team-artifacts }services:
session: { backend: postgres, url: ${DATABASE_URL} }
memory: { backend: postgres, url: ${DATABASE_URL} }
artifacts: { backend: s3, bucket: prod-artifacts }
# Multiple workers behind load balancer
# Application owns gateway layer
# Agents deployed via Admin APIAt each stage, agent definitions remain unchanged — only duragent.yaml changes.