Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/dallay/corvus/llms.txt

Use this file to discover all available pages before exploring further.

Extension Overview

Corvus is designed for extreme extensibility. Every core subsystem is a trait — you can swap implementations with zero changes to the agent runtime.

Extension Points

From README.md:
SubsystemTraitWhat You Can Build
AI ModelsProviderCustom LLM backends, local models, API wrappers
ChannelsChannelMessaging platforms, custom protocols
ToolsToolNew capabilities, integrations, hardware drivers
MemoryMemoryStorage backends, vector DBs, knowledge graphs
RuntimeRuntimeAdapterExecution environments (WASM, edge, embedded)
ObservabilityObserverMetrics, tracing, monitoring

Architecture

All extensions follow the same pattern:
1

Implement the Trait

Create a struct that implements the trait (e.g., Provider, Tool)
2

Register in Factory

Add your implementation to the factory module (e.g., src/providers/mod.rs)
3

Configure

Add config options to config.toml
4

Use

Agent automatically discovers and uses your extension

Quick Examples

Custom Provider (~30 lines)

use corvus::providers::traits::Provider;
use async_trait::async_trait;

pub struct MyProvider {
    api_key: String,
    client: reqwest::Client,
}

#[async_trait]
impl Provider for MyProvider {
    async fn chat_with_system(
        &self,
        system: Option<&str>,
        message: &str,
        model: &str,
        temperature: f64,
    ) -> anyhow::Result<String> {
        // Your API call here
        Ok("response".into())
    }
}

Custom Tool (~50 lines)

use corvus::tools::traits::{Tool, ToolResult};
use async_trait::async_trait;

pub struct MyTool;

#[async_trait]
impl Tool for MyTool {
    fn name(&self) -> &str { "my_tool" }
    
    fn description(&self) -> &str {
        "Does something useful"
    }
    
    fn parameters_schema(&self) -> serde_json::Value {
        serde_json::json!({
            "type": "object",
            "properties": {
                "input": {"type": "string"}
            },
            "required": ["input"]
        })
    }
    
    async fn execute(&self, args: serde_json::Value) 
        -> anyhow::Result<ToolResult> 
    {
        let input = args["input"].as_str().unwrap();
        Ok(ToolResult {
            success: true,
            output: format!("Processed: {}", input),
            error: None,
        })
    }
}

Custom Channel (~100 lines)

use corvus::channels::traits::{Channel, ChannelMessage, SendMessage};
use async_trait::async_trait;

pub struct MyChannel {
    api_url: String,
    client: reqwest::Client,
}

#[async_trait]
impl Channel for MyChannel {
    fn name(&self) -> &str { "my_channel" }
    
    async fn send(&self, message: &SendMessage) -> anyhow::Result<()> {
        // Send message via API
        Ok(())
    }
    
    async fn listen(&self, tx: tokio::sync::mpsc::Sender<ChannelMessage>) 
        -> anyhow::Result<()> 
    {
        // Poll for incoming messages, send to tx
        Ok(())
    }
}

Extension Guidelines

1. Security First

Always validate inputs before executing. Don’t trust LLM-provided parameters.
  • Validate and sanitize all inputs
  • Use parameterized queries (SQL, etc.)
  • Never log secrets or API keys
  • Implement rate limiting where applicable

2. Error Handling

Return errors via Result or structured error types. Never panic in production code.
// Good
let value = args["param"]
    .as_str()
    .ok_or_else(|| anyhow::anyhow!("Missing 'param'"))?;

// Bad
let value = args["param"].as_str().unwrap();  // can panic

3. Async Operations

Use async/await for I/O operations. Never block the agent loop.
#[async_trait]
impl Tool for HttpTool {
    async fn execute(&self, args: Value) -> Result<ToolResult> {
        let resp = reqwest::get(url).await?;  // non-blocking
        Ok(ToolResult::success(resp.text().await?))
    }
}

4. Testing

Write tests for every extension. Use #[tokio::test] for async tests.
#[tokio::test]
async fn test_my_tool() {
    let tool = MyTool::new();
    let result = tool.execute(json!({"input": "test"})).await.unwrap();
    assert!(result.success);
}

Example Extensions

Corvus ships with working examples:

Custom Provider

Ollama local LLM integration

Custom Channel

Telegram bot integration

Custom Tool

HTTP GET tool

Custom Memory

In-memory backend

Development Workflow

  1. Clone the repository
    git clone https://github.com/dallay/corvus.git
    cd corvus/clients/agent-runtime
    
  2. Create your extension
    # Providers
    touch src/providers/my_provider.rs
    
    # Tools
    touch src/tools/my_tool.rs
    
    # Channels
    touch src/channels/my_channel.rs
    
  3. Register in module
    // src/providers/mod.rs
    pub mod my_provider;
    
    pub fn create_provider(name: &str, config: &Config) 
        -> Result<Box<dyn Provider>> 
    {
        match name {
            "my_provider" => Ok(Box::new(my_provider::MyProvider::new(config))),
            // ...
        }
    }
    
  4. Test
    cargo test
    
  5. Use
    # ~/.corvus/config.toml
    default_provider = "my_provider"
    

Community Extensions

Contributing extensions:
  1. Follow Contributing Guidelines
  2. Add tests and documentation
  3. Submit a pull request
  4. Add to the Extensions Registry

Next Steps

Custom Provider

Build a custom LLM provider

Custom Channel

Build a custom messaging channel

Custom Tool

Build a custom tool

Custom Memory

Build a custom memory backend

Build docs developers (and LLMs) love