Tenzro Testnet is live —request testnet TNZO
← Back to Tutorials

Build a Network-Level Plugin Agent

AI AgentsIntermediate35 min

Build an infrastructure-level plugin agent that other applications can discover and call via the Tenzro marketplace. Design your agent as a reusable service, register a template with pricing, publish to the agent marketplace, handle task delegations via micropayment channels, and monitor usage and earnings. Your plugin becomes a network-level service that any app on Tenzro can invoke.

What You'll Build

  • A reusable plugin agent (sentiment analysis as the example)
  • An agent template with per-invocation and subscription pricing
  • Marketplace publishing for discovery by other apps
  • Task delegation handling with automatic result delivery
  • Micropayment channel billing (per-call fees)
  • Usage monitoring and revenue tracking

Prerequisites

[dependencies]
tenzro-sdk = "0.1"
tokio = { version = "1", features = ["full"] }
serde_json = "1"
tracing-subscriber = "0.3"

Step 1: Design the Plugin Agent

A plugin agent is a regular Tenzro agent designed as a reusable infrastructure service. It has a well-defined input/output contract and is discoverable via the marketplace:

use tenzro_sdk::{TenzroClient, config::SdkConfig};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let config = SdkConfig::testnet();
    let client = TenzroClient::connect(config).await?;

    // Register identity and wallet
    let identity = client.identity().register_human("Plugin Developer").await?;
    let wallet = client.wallet().create_wallet().await?;

    // Register a machine agent for the plugin
    let agent = client.agent().register(
        "sentiment-analyzer",
        "Real-Time Sentiment Analysis Plugin",
        &["nlp", "sentiment", "analysis"],
    ).await?;

    println!("Plugin Agent: {}", agent.agent_id);
    println!("DID: did:tenzro:machine:{}:{}", identity.did, agent.agent_id);

Step 2: Register Agent Template with Pricing

Templates are declarative agent blueprints. By registering a template, other developers can discover your plugin and spawn instances of it:

// Define an agent template for the marketplace
// Templates are declarative blueprints that other apps can discover and spawn
let template = client.marketplace().register_template(
    "sentiment-analyzer-v1",           // template_id
    "Real-Time Sentiment Analyzer",    // name
    "1.0.0",                           // version
    "Analyzes text sentiment in real-time. Returns score (-1.0 to 1.0), \
     confidence, and detected emotions. Supports 12 languages.",
    &["nlp", "sentiment", "analysis", "multi-language"],  // capabilities
).await?;

println!("Template registered: {}", template.template_id);
// Set pricing for your plugin
// Users pay per invocation via micropayment channels
client.marketplace().set_template_pricing(
    "sentiment-analyzer-v1",
    PricingModel {
        // Per-invocation fee
        base_fee: 0.01,          // 0.01 TNZO per call
        // Per-token fee for input text
        per_token_fee: 0.0001,   // 0.0001 TNZO per input token
        // Optional subscription tier
        subscription: Some(SubscriptionTier {
            monthly_fee: 50.0,   // 50 TNZO/month
            included_calls: 10000,
            overage_rate: 0.005, // discounted per-call rate
        }),
    },
).await?;

println!("Pricing set:");
println!("  Base fee:    0.01 TNZO/call");
println!("  Token fee:   0.0001 TNZO/token");
println!("  Subscription: 50 TNZO/month (10K calls included)");

Step 3: Publish to the Marketplace

Publishing makes your template discoverable. Other apps can search, inspect, and call your plugin:

# Publish the template to the agent marketplace
tenzro marketplace register \
  --template-id sentiment-analyzer-v1 \
  --name "Real-Time Sentiment Analyzer" \
  --version 1.0.0 \
  --description "Analyzes text sentiment. Returns score, confidence, emotions." \
  --capabilities nlp,sentiment,analysis \
  --pricing-base 0.01 \
  --pricing-per-token 0.0001

# Output:
# Template registered to marketplace
#   Template ID: sentiment-analyzer-v1
#   Status:      Published
#   Discoverable via: tenzro marketplace list

# List marketplace templates
tenzro marketplace list

# Output:
# Agent Marketplace:
#   sentiment-analyzer-v1     Real-Time Sentiment Analyzer   0.01 TNZO/call
#   ref-bridge-arbitrage-v1   Bridge Arbitrage Scanner       0.05 TNZO/call
#   code-review-agent-v1      Automated Code Review          0.02 TNZO/call

Step 4: Handle Incoming Task Delegations

When another application posts a task targeting your template, your agent receives it, processes it, and returns the result. The payment is handled automatically via micropayment channels:

use tenzro_sdk::{TenzroClient, config::SdkConfig};

// Handle incoming task delegations
// When another app calls your plugin, the task arrives here
async fn handle_task(
    client: &TenzroClient,
    agent_id: &str,
) -> Result<(), Box<dyn std::error::Error>> {
    // Listen for incoming tasks
    loop {
        let tasks = client.task().list_tasks_for_agent(agent_id).await?;

        for task in tasks {
            println!("Incoming task: {}", task.task_id);
            println!("  From: {}", task.requester);
            println!("  Input: {}", task.input);

            // Process the task (your business logic)
            let sentiment = analyze_sentiment(&task.input)?;

            // Submit the result
            client.task().complete_task(
                &task.task_id,
                &serde_json::json!({
                    "score": sentiment.score,
                    "confidence": sentiment.confidence,
                    "emotions": sentiment.emotions,
                    "language": sentiment.detected_language,
                }).to_string(),
            ).await?;

            println!("  Result: score={:.2}, confidence={:.2}",
                sentiment.score, sentiment.confidence);
        }

        tokio::time::sleep(std::time::Duration::from_secs(2)).await;
    }
}

struct SentimentResult {
    score: f64,        // -1.0 (negative) to 1.0 (positive)
    confidence: f64,   // 0.0 to 1.0
    emotions: Vec<String>,
    detected_language: String,
}

fn analyze_sentiment(text: &str) -> Result<SentimentResult, Box<dyn std::error::Error>> {
    // Your NLP model inference here
    // Could use a local model or call a served model on the network
    Ok(SentimentResult {
        score: 0.82,
        confidence: 0.95,
        emotions: vec!["joy".into(), "optimism".into()],
        detected_language: "en".into(),
    })
}

Step 5: Micropayment Billing

Each invocation is billed through a micropayment channel. The caller opens a channel with a deposit, and each call deducts from it. No on-chain transaction per call — settlement happens when the channel closes:

// Set up micropayment channel for per-invocation billing
// The escrow system handles payment automatically:
//   1. Caller opens a micropayment channel with your agent
//   2. Each invocation deducts from the channel balance
//   3. You can close the channel to settle accumulated earnings

// Open an escrow channel (done by the caller, shown for reference)
let channel = client.settlement().open_channel(
    &caller_wallet,          // payer
    &plugin_wallet,          // payee (your plugin)
    1000,                    // initial deposit (TNZO)
    86400,                   // timeout (24 hours)
).await?;

// After processing tasks, close the channel to settle
let settlement = client.settlement().close_channel(
    &channel.channel_id,
).await?;
println!("Channel settled: {} TNZO earned", settlement.amount);

Payment flow. Micropayment channels allow thousands of per-call billings without on-chain gas costs for each one. The channel deposit is locked in escrow, each call updates the off-chain balance, and the final settlement happens on-chain when either party closes the channel. The 0.5% network fee is applied at settlement.

Step 6: How Other Apps Call Your Plugin

Here is the caller's perspective — discovering your plugin in the marketplace and posting a task:

// How other apps discover and call your plugin

// 1. Search the marketplace
let templates = client.marketplace().list_templates().await?;
let sentiment = templates.iter()
    .find(|t| t.template_id == "sentiment-analyzer-v1")
    .expect("Template not found");

println!("Found: {} ({})", sentiment.name, sentiment.pricing.base_fee);

// 2. Get template details
let details = client.marketplace().get_template("sentiment-analyzer-v1").await?;
println!("Description: {}", details.description);
println!("Capabilities: {:?}", details.capabilities);

// 3. Post a task to the plugin
let task = client.task().post_task(
    "sentiment-analyzer-v1",     // target template
    "The new product launch exceeded all expectations and \
     customer feedback has been overwhelmingly positive.",
    100,                          // max payment (TNZO micros)
).await?;

// 4. Wait for the result
let result = client.task().get_task(&task.task_id).await?;
println!("Sentiment: {}", result.output);
Found: Real-Time Sentiment Analyzer (0.01 TNZO/call)
Description: Analyzes text sentiment. Returns score, confidence, emotions.
Capabilities: ["nlp", "sentiment", "analysis", "multi-language"]

Sentiment: {"score": 0.92, "confidence": 0.97, "emotions": ["joy", "excitement"], "language": "en"}

Or via the CLI:

# Discover and call the plugin via CLI
tenzro marketplace get sentiment-analyzer-v1

# Post a task
tenzro task post \
  --template sentiment-analyzer-v1 \
  --input "The market conditions are uncertain but our strategy is solid." \
  --max-payment 1

# Get result
tenzro task get task-abc123

# Output:
# Task: task-abc123
#   Status:  completed
#   Output:  {"score": 0.35, "confidence": 0.88, "emotions": ["caution", "confidence"]}
#   Cost:    0.012 TNZO
#   Latency: 145ms

Step 7: Monitor Usage and Earnings

# Monitor usage and earnings
tenzro provider status

# Output:
# Plugin Agent Status:
#   Agent ID:        sentiment-analyzer
#   Template:        sentiment-analyzer-v1
#   Status:          Active
#   Uptime:          12h 45m
#
#   Usage (last 24h):
#     Total Calls:     2,847
#     Unique Callers:  43
#     Avg Latency:     132ms
#     Error Rate:      0.1%
#
#   Revenue (last 24h):
#     Per-call fees:   28.47 TNZO
#     Token fees:      14.23 TNZO
#     Subscriptions:   150.00 TNZO
#     Total Revenue:   192.70 TNZO
#     Network Fee:     0.96 TNZO (0.5%)
#     Net Earnings:    191.74 TNZO
#
#   Open Channels:     12
#   Channel Balance:   1,847 TNZO (pending settlement)

Full Example

Run with:

cargo run --example network_plugin_agent
View full source
use tenzro_sdk::{TenzroClient, config::SdkConfig};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    tracing_subscriber::fmt::init();
    let client = TenzroClient::connect(SdkConfig::testnet()).await?;

    // 1. Identity & Wallet
    let identity = client.identity().register_human("Plugin Developer").await?;
    let wallet = client.wallet().create_wallet().await?;

    // 2. Register agent
    let agent = client.agent().register(
        "sentiment-analyzer", "Sentiment Analysis Plugin",
        &["nlp", "sentiment"],
    ).await?;

    // 3. Publish template
    let template = client.marketplace().register_template(
        "sentiment-analyzer-v1", "Real-Time Sentiment Analyzer",
        "1.0.0", "Analyzes text sentiment with score and emotions.",
        &["nlp", "sentiment", "analysis"],
    ).await?;

    // 4. Handle incoming tasks
    println!("Plugin live. Waiting for tasks...");
    loop {
        let tasks = client.task().list_tasks_for_agent(&agent.agent_id).await?;
        for task in tasks {
            let result = analyze_sentiment(&task.input)?;
            client.task().complete_task(&task.task_id, &format!(
                r#"{{"score":{:.2},"confidence":{:.2}}}"#,
                result.score, result.confidence,
            )).await?;
        }
        tokio::time::sleep(std::time::Duration::from_secs(2)).await;
    }
}

What You Learned

Next Steps