Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 getHow
Sessions that survive crashesAppend-only event log, attach/detach like tmux
Agents you can read and versionYAML + Markdown — no code required
State you can inspectJust files on disk — cat, grep, git diff
Deploy anywhereSingle binary, ~10MB, no Python/Node/Docker
Your choice of partsSwap 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

ComponentDefaultSwappable
GatewaysCLI, HTTP, SSE, Telegram, DiscordAny platform via gateway plugins
LLMOpenRouterAnthropic, OpenAI, Ollama, or any provider
SandboxTrust modebubblewrap, Docker (planned)
StorageFilesystemPostgres, 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

MethodProvidersStorageAuto-Refresh
OAuth loginAnthropic only~/.duragent/auth.jsonYes
Environment variableAll providersShell environmentNo

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

  1. A URL is printed — open it in your browser
  2. Authorize the application on Anthropic’s site
  3. Copy the returned code and paste it back into the terminal
  4. 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.json to version control.

Environment Variables

For providers that don’t support OAuth login, set the API key as an environment variable:

VariableProvider
ANTHROPIC_API_KEYAnthropic (Claude)
OPENROUTER_API_KEYOpenRouter
OPENAI_API_KEYOpenAI-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:

  1. OAuth token from ~/.duragent/auth.json (if present and valid)
  2. ANTHROPIC_API_KEY environment variable

For all other providers, only the environment variable is checked.

Troubleshooting

ProblemSolution
“Unsupported provider” from duragent loginOnly anthropic supports OAuth. Use environment variables for other providers.
OAuth token expiredTokens 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 effectRestart 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?

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:

FilePurposeWhen Loaded
SOUL.md“Who the agent IS” — identity and personalityEvery turn
SYSTEM_PROMPT.md“What the agent DOES” — core system promptEvery turn
INSTRUCTIONS.mdAdditional runtime instructionsEvery 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:

ModeSourceUse Case
StaticFiles in .duragent/agents/GitOps, version-controlled agents
DynamicAdmin APIAPI-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 SessionsWith Sessions
Crash = context lostCrash = reconnect and continue
One client onlyAccess from CLI, HTTP, Telegram, web
Tied to terminalPortable across devices
No history APIQuery conversation history programmatically

Session States

StateDescription
activeRunning, client connected
runningRunning, client disconnected (continue mode)
pausedWaiting for reconnect (pause mode)
completedCompleted or explicitly ended

Disconnect Behavior

When a client disconnects, the agent’s behavior is configurable per-agent:

ModeBehaviorUse Case
continueAgent keeps executing, buffers outputAsync workflows, fire-and-forget tasks
pauseAgent pauses, waits for reconnectInteractive 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
ModeBehavior
discardRemove events covered by the snapshot
archiveMove old events to events.archive.jsonl before removing
disabledNo compaction (events grow unbounded)

Gateway Commands

In gateway chats (Telegram, Discord), you can use slash commands:

CommandBehavior
/resetEnd current session; next message starts fresh
/statusShow 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:

GatewayUse Case
CLILocal development, duragent chat, duragent attach
HTTP RESTProgrammatic access, webhooks, Admin API
SSEReal-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:

DirectionMessage Types
Duragent to pluginsend_message, send_media, send_typing, edit_message, delete_message, answer_callback_query, ping, shutdown
Plugin to Duragentready, 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

GatewayStatusCrate
CLIBuilt-induragent
HTTP RESTBuilt-induragent
SSEBuilt-induragent
TelegramPluginduragent-gateway-telegram
DiscordPluginduragent-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

FieldTypeRequiredDescription
namestringYesUnique identifier (alphanumeric + hyphens)
descriptionstringNoHuman-readable description
versionstringNoSemantic version
labelsmapNoKey-value labels for filtering

spec.model

FieldTypeRequiredDescription
providerstringYesopenrouter, openai, anthropic, or ollama
namestringYesModel name/identifier
temperaturefloatNoSampling temperature (0-2, default 0.7)
max_input_tokensintNoCap input tokens (for cost control)
max_output_tokensintNoMax response tokens
base_urlstringNoOverride provider’s base URL

Prompt Files

FieldPoints ToPurpose
soulMarkdown file“Who the agent IS” — identity and personality
system_promptMarkdown file“What the agent DOES” — core system prompt
instructionsMarkdown fileAdditional 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

FieldTypeDefaultDescription
on_disconnectstringpausecontinue or pause
max_tool_iterationsint10Max tool call iterations per request
llm_timeout_secondsint300Timeout for LLM requests in seconds
ttl_hoursint(global)Per-agent session TTL override
compactionstring(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.

FieldTypeDefaultDescription
max_history_tokensint20000Max tokens for conversation history (0 = no cap)
max_tool_result_tokensint8000Max tokens per tool result
tool_result_truncationstringheadhead, tail, or both
tool_result_keep_firstint2First N tool results kept visible
tool_result_keep_lastint5Last 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:

VersionStability
duragent/v1alpha1Alpha (current, breaking changes allowed)
duragent/v1beta1Beta (mostly stable)
duragent/v1Stable (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

TypeDescriptionBest For
Built-inBundled with DuragentCore operations (e.g., bash)
CLICustom scripts with optional READMESimple extensions, any language
MCPModel Context Protocol serversComplex 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

NameDescription
bashExecute shell commands in sandbox
web_searchSearch the web (requires BRAVE_API_KEY)
web_fetchFetch a URL and convert to Markdown
schedule_taskCreate a scheduled task
list_schedulesList active schedules
cancel_scheduleCancel a schedule by ID

Memory tools (recall, remember, reflect, update_world) are automatically registered when memory is configured. See Memory.

Searches the web using the Brave Search API.

  • Parameters: query (string, required), count (integer, 1–20, default 5)
  • Requires: BRAVE_API_KEY environment 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 — http and https only)
  • 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).

FieldRequiredDescription
typeYescli
nameYesTool identifier
commandYesScript path (relative to agent directory)
descriptionNoShort description shown to LLM
readmeNoPath 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

ModeDeny ListAllow ListUnknown Commands
dangerousBlocks (always)IgnoredAllowed
askBlocks (always)Auto-approvedRequires human approval
restrictBlocks (always)AllowedDenied

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:

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

  1. Agent requests tool invocation
  2. User sees an approval prompt
  3. User chooses: Allow Once, Allow Always, or Deny
  4. “Allow Always” saves the pattern to policy.local.yaml for future auto-approval

Merge Behavior

When both policy.yaml and policy.local.yaml exist:

FieldStrategy
modeLocal overrides base (unless dangerous)
denyLists merged (union)
allowLists merged (union)
notifyLists 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)
PathOwnerPurpose
memory/world/*.mdSharedObjective facts about the world
agents/{name}/memory/MEMORY.mdAgentCurated learnings
agents/{name}/memory/daily/*.mdAgentDaily 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

ToolActionWhen to Use
recallRead memory contextStart of conversation, when context needed
rememberAppend to daily logAfter learning something
reflectRewrite MEMORY.mdEnd of session, consolidate learnings
update_worldWrite world knowledge topicNew 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 content is provided, atomic write to memory/MEMORY.md. When omitted, reads and returns the current memory/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 TypeExpected SizeManagement
world/*.md1-10 KB eachAgent rewrites during update_world
MEMORY.md2-10 KBAgent rewrites during reflect
daily/*.md0.5-2 KB/dayOld 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

TypeDescriptionExample
One-shot (at)Fire once at a specific time2026-02-01T09:00:00Z
Interval (every)Repeat every N seconds3600 (hourly)
CronStandard cron expression0 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

TypeDescription
messageSend text directly (no LLM call)
taskExecute with agent tools, summarize results

Example Scenarios

One-Time Reminder

  1. User: “Remind me to review the PR in 2 hours”
  2. Agent calls schedule_task with at: "2026-01-30T16:00:00Z"
  3. Two hours later, user receives: “Reminder: review the PR”

Recurring Prompt

  1. User: “Send me a daily standup prompt at 9am on weekdays”
  2. Agent calls schedule_task with cron: "0 9 * * MON-FRI"
  3. Every weekday at 9am, user receives the prompt
  4. 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

FieldTypeDefaultDescription
namestringrequiredGateway identifier
commandstringrequiredPath to gateway binary
argsarray[]Command arguments
envmap{}Environment variables
restartenumon_failurealways, 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

FieldDescriptionExamples
gatewayGateway nametelegram, discord
chat_typeConversation typedm, group, channel
chat_idSpecific chat ID-1001234567890
sender_idSpecific user ID123456789

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

EventPurpose
readyGateway initialized
message_receivedIncoming user message
callback_queryInline keyboard button pressed
command_okCommand succeeded
command_errorCommand failed
pongResponse to health check ping
errorGateway-level error
auth_requiredAuthentication needed (e.g. QR code)
auth_successAuthentication succeeded
shutdownGateway terminating

Commands (Duragent to gateway):

CommandPurpose
send_messageSend text to a chat
send_mediaSend media (image, video, audio, document)
send_typingShow typing indicator
edit_messageEdit a previously sent message
delete_messageDelete a message
answer_callback_queryRespond to inline keyboard button press
pingHealth check
shutdownGraceful termination request

Message Metadata

Gateways provide metadata with incoming messages:

  • chat_id — Platform-specific chat identifier
  • chat_typedm, group, or channel
  • sender_id — User identifier
  • mentions_bot — Whether the bot was @mentioned
  • reply_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:

PolicyBehaviorUse Case
openAccept all DMs (default)Public bot
disabledReject all DMsGroup-only bot
allowlistOnly listed user IDsPrivate 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:

PolicyBehaviorUse Case
openParticipate in any group (default)Public bot
disabledIgnore all group messagesDM-only bot
allowlistOnly listed groupsControlled 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.

ModeBehavior
mention (default)Only respond when @mentioned or replied to
alwaysRespond to every allowed message
spec:
  access:
    groups:
      activation: mention

Mention detection supports:

  • Explicit @bot_username in message text
  • Reply to bot’s previous message

Per-Sender Policies

Within groups, you can assign different dispositions to different senders:

DispositionLLM Sees It?Triggers Response?Use Case
allowYesYesNormal interaction
passiveYes (future turns)NoContext without triggering
silentNoNoAudit trail only
blockNoNoSpam, 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
ModeStorageSurvives Crash?Token Cost
silentIn-memory bufferNoLow (only on trigger)
passiveConversation historyYesAccumulates 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

ModeBehaviorUse Case
batchCombine all pending into one requestRapid message senders
sequentialProcess one at a time until emptyPreserve individual context
dropDiscard pending messagesSimple, predictable

Overflow Strategies

StrategyBehavior
drop_oldEvict oldest message to make room
drop_newSilently discard the new message
rejectDiscard 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

ConceptPurposeFormat
ToolA capability the agent can invokeCLI command or built-in (MCP planned)
SkillInstructions for how to accomplish a taskSKILL.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

FormatUse Case
JSONLEvent streams (append-only, fast writes)
YAMLStructured state (snapshots, schedules)
MarkdownProse 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

  1. Message @BotFather on Telegram
  2. Send /newbot and follow the prompts
  3. 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

  1. Go to Discord Developer Portal
  2. Create a New Application
  3. Go to Bot tab, click Add Bot
  4. Copy the bot token
  5. Under Privileged Gateway Intents, enable Message Content Intent
  6. Go to OAuth2 > URL Generator, select bot scope with Send Messages and Read Message History permissions
  7. 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:

CommandDescription
/quit or /exitEnd session
Ctrl+DDetach 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:

SyntaxBehavior
${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.

VariableRequiredDescription
ANTHROPIC_API_KEYNoAPI key for Anthropic (Claude). Not needed if using OAuth login.
OPENAI_API_KEYNoAPI key for OpenAI-compatible providers
OPENROUTER_API_KEYNoAPI 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.

VariableRequiredDescription
BRAVE_API_KEYNoBrave 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_token field in duragent.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.

VariableBinaryDescription
DISCORD_BOT_TOKENduragent-discordDiscord bot token
TELEGRAM_BOT_TOKENduragent-telegramTelegram bot token

Tip: When running gateways as external plugins (via gateways.external[]), you can forward these through the env field in duragent.yaml using ${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

FieldTypeDefaultDescription
server.hoststring127.0.0.1Bind address
server.portu168080HTTP port
server.request_timeout_secondsu64300Non-streaming request timeout
server.idle_timeout_secondsu6460SSE idle timeout
server.keep_alive_interval_secondsu6415SSE keep-alive interval
server.admin_tokenstring?noneAdmin API token
server.api_tokenstring?noneAPI token. If set, API endpoints require this token. If not set, only localhost requests are accepted.
server.max_connectionsusize1024Maximum concurrent connections

Workspace

FieldTypeDefaultDescription
workspacepath?.duragentWorkspace root directory
agents_dirpath?{workspace}/agentsAgent definitions directory
services.session.pathpath?{workspace}/sessionsSession storage directory
world_memory.pathpath?{workspace}/memory/worldShared world memory directory

Sessions

FieldTypeDefaultDescription
sessions.ttl_hoursu64168Hours of inactivity before session expiry. 0 disables.
sessions.compactionenumdiscarddiscard, archive, or disabled

Gateways

FieldTypeDefaultDescription
gateways.telegram.enabledbooltrueEnable Telegram gateway (requires gateway-telegram feature)
gateways.telegram.bot_tokenstringrequiredTelegram bot token
gateways.discord.enabledbooltrueEnable Discord gateway (requires gateway-discord feature)
gateways.discord.bot_tokenstringrequiredDiscord bot token
gateways.external[].namestringrequiredGateway identifier
gateways.external[].commandstringrequiredPath to gateway binary
gateways.external[].argsarray[]Command arguments
gateways.external[].envmap{}Environment variables
gateways.external[].restartenumon_failurealways, on_failure, or never

Routes

FieldTypeDescription
routes[].matchobjectMatch conditions (all must match, AND logic)
routes[].agentstringAgent to route to

Match conditions:

FieldDescription
gatewayGateway name (telegram, discord)
chat_typedm, group, or channel
chat_idSpecific chat ID
sender_idSpecific user ID

Routes are evaluated top-to-bottom; first match wins. An empty match: {} acts as a catch-all.

Sandbox

FieldTypeDefaultDescription
sandbox.modestringtrusttrust (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 a Bearer token via the Authorization header.
  • 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

CodeHTTP StatusDescription
not_found404Resource not found
invalid_request400Malformed request
unauthorized401Missing or invalid auth
forbidden403Insufficient permissions
conflict409Resource conflict
internal_error500Server error
session_not_found404Session does not exist
session_ended410Session has been ended
agent_not_found404Agent does not exist