Build a Network-Level Plugin Agent
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/callStep 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: 145msStep 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_agentView 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
- Plugin design — structuring an agent as a reusable infrastructure service with clear I/O contracts
- Template registration — creating declarative agent blueprints for the marketplace
- Pricing models — per-invocation fees, per-token rates, and subscription tiers
- Task handling — receiving, processing, and completing delegated tasks
- Micropayments — off-chain billing via payment channels with on-chain settlement
- Marketplace discovery — how callers find and invoke your plugin
- Revenue monitoring — tracking usage, earnings, and channel balances
Next Steps
- See the Task Marketplace tutorial for the full task lifecycle
- See the Compose Agent from Registry tutorial for building agents that call plugins
- Read the End-to-End Agent Demo for the complete agent lifecycle