Skip to content

Latest commit

 

History

History
574 lines (463 loc) · 18.8 KB

File metadata and controls

574 lines (463 loc) · 18.8 KB

Duragent Deployment Plan

This document describes Duragent's deployment modes, storage configuration, and gateway setup.

Deployment Philosophy

"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:

Simple Mode (Self-Hosted Power Users)

┌──────────────────────────────────────────────────────────────┐
│                    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

Complex Mode

┌─────────────────────────────────────────────────────────────┐
│                      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

Why This Philosophy Works

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

Storage Modes

Duragent supports two state modes: file-based (default) and external backends.

File-Based State (Default)

# 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

External Mode

# 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-1

Storage Interface

Duragent 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,
}

Switching Modes

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} }

Gateway Configuration

Duragent separates core gateways (built-in protocols) from platform gateways (plugins).

Core Gateways

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 Gateways (Plugins)

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}

Simple Mode (Embedded Plugins)

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:

  1. Duragent starts the gateway plugin subprocess
  2. Plugin receives message from Telegram
  3. Plugin sends message to Duragent via Gateway Protocol (JSON over stdio)
  4. Duragent routes to the configured agent
  5. Agent processes, returns response via Gateway Protocol
  6. Plugin sends response back to Telegram

Complex Mode (External Gateway)

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 routing

How it works:

  1. Application (e.g., Rails) receives webhook from Telegram
  2. Application identifies user from chat_id
  3. Application looks up user's agent configuration
  4. Application calls Duragent worker via HTTP REST API
  5. Application streams response via SSE
  6. 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

Session Configuration

Sessions follow tmux-like semantics: disconnection does not stop the session.

Disconnect Behavior

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

Session States

State Description
active Running, client attached
running Running, client disconnected (continue mode)
paused Paused, waiting for reconnect (pause mode)
ended Completed or explicitly ended

Async Workflow Example

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_user

User 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

Quick Start Examples

Minimal Self-Hosted Setup

# 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

With Telegram Gateway

# 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

Production Setup (External Backends)

# 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 serve

Migration Path

Stage 1: Local Development

services:
  session: { backend: filesystem }
  memory: { backend: filesystem }
  artifacts: { backend: filesystem }

Stage 2: Shared State (Small Team)

services:
  session: { backend: postgres, url: ${DATABASE_URL} }
  memory: { backend: filesystem }  # Keep local for easy editing
  artifacts: { backend: s3, bucket: team-artifacts }

Stage 3: Full Production

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 API

At each stage, agent definitions remain unchanged — only duragent.yaml changes.