Compose an Agent from the Registry
The Tenzro Hub registry is more than a directory — it's a composition surface. Every skill, tool, and agent template published to the chain can be discovered by JSON-RPC, filtered programmatically, and assembled into a new autonomous agent in a single flow. This tutorial walks through the exact sequence we used to compose a research-capable agent from existing registry entries and invoke it end-to-end.
Unlike the Discover Hub tutorial, which focuses on reading the registry, this one emphasises composition: picking compatible entries, wiring them together, and turning them into a running agent whose capabilities are a superset of any individual registry item.
Prerequisites
- Node.js 18+ and
curlwithjq - A funded wallet with at least 1 TNZO (complete Create an Agentic Wallet first)
- Familiarity with the Discover Hub tutorial
1. Inventory the registry
Start by counting what's available. On a freshly bootstrapped testnet, all three registries will be empty. As soon as one provider publishes a skill or a template, it becomes visible to every other node within one gossip round.
# Enumerate every skill published to the on-chain registry
curl -s https://rpc.tenzro.network \
-X POST \
-H "Content-Type: application/json" \
-d '{
"jsonrpc":"2.0",
"id":1,
"method":"tenzro_listSkills",
"params":{}
}' | jq '.result | length'
# Typical response on a populated testnet:
# 8Tools are the next layer — callable utilities that agents use to act on the world. Each tool carries a tool_type, tags, and optional pricing:
curl -s https://rpc.tenzro.network \
-X POST \
-H "Content-Type: application/json" \
-d '{
"jsonrpc":"2.0",
"id":2,
"method":"tenzro_listTools",
"params":{}
}' | jq '.result[] | {tool_id, name, tool_type, tags}'Finally, the agent templates. These bundle a set of declared capabilities and a default agent_type:
curl -s https://rpc.tenzro.network \
-X POST \
-H "Content-Type: application/json" \
-d '{
"jsonrpc":"2.0",
"id":3,
"method":"tenzro_listAgentTemplates",
"params":{}
}' | jq '.result[] | {template_id, name, agent_type, capabilities}'2. Load the registry from TypeScript
The website ships with typed helpers in src/lib/rpc.ts. All three listX() functions return empty arrays on failure (they never throw), which means you can safely compose them with Promise.all and use the results directly in any Next.js server component or standalone Node script.
import {
listSkills,
listTools,
listAgentTemplates,
type SkillInfo,
type ToolInfo,
type AgentTemplateInfo,
} from "@/lib/rpc";
// Fetch all three registries concurrently
async function loadRegistry() {
const [skills, tools, templates] = await Promise.all([
listSkills(),
listTools(),
listAgentTemplates(),
]);
return { skills, tools, templates };
}
// Filter for only entries that match a required capability
function findCompatible(
skills: SkillInfo[],
tools: ToolInfo[],
templates: AgentTemplateInfo[],
capability: string
) {
const matchingSkills = skills.filter(
(s) =>
s.capabilities.includes(capability) &&
(s.status ?? "active").toLowerCase() === "active"
);
const matchingTools = tools.filter((t) =>
(t.tags ?? []).includes(capability)
);
const matchingTemplates = templates.filter((t) =>
t.capabilities.includes(capability)
);
return { matchingSkills, matchingTools, matchingTemplates };
}3. Build the composition spec
Once you have typed results, composing an agent is just a filter and an object literal. The spec below picks a search skill, an HTTP tool, and a research-capable template, then emits a JSON object that the node will accept as input to tenzro_spawnAgent.
// Full workflow: load → filter → compose → spawn
async function composeResearchAgent() {
const { skills, tools, templates } = await loadRegistry();
// Step 1: find a web-search skill
const searchSkill = skills.find(
(s) => s.name === "web-search" || s.capabilities.includes("search")
);
if (!searchSkill) throw new Error("No search skill available");
// Step 2: find an HTTP fetcher tool
const httpTool = tools.find(
(t) => t.name === "http-get" || (t.tags ?? []).includes("http")
);
if (!httpTool) throw new Error("No HTTP tool available");
// Step 3: find a research-capable agent template
const template = templates.find(
(t) =>
t.capabilities.includes("research") ||
t.capabilities.includes("payments")
);
if (!template) throw new Error("No compatible template");
// Step 4: assemble the composition spec
return {
template_id: template.template_id,
skills: [searchSkill.skill_id],
tools: [httpTool.tool_id],
metadata: {
purpose: "research",
composed_at: Date.now(),
},
};
}4. Spawn the agent on-chain
With the composition spec in hand, call tenzro_spawnAgent to instantiate a new agent backed by a fresh machine DID and wallet. The node persists the agent to the registry and returns an agent_id that every other peer will see after the next gossip round.
# Spawn an agent from the composed spec
COMPOSITION='{
"template_id": "'"$TEMPLATE_ID"'",
"skills": ["'"$SKILL_ID"'"],
"tools": ["'"$TOOL_ID"'"],
"metadata": {"purpose": "research"}
}'
curl -s https://rpc.tenzro.network \
-X POST \
-H "Content-Type: application/json" \
-d '{
"jsonrpc":"2.0",
"id":1,
"method":"tenzro_spawnAgent",
"params":'"$COMPOSITION"'
}' | jq5. Verify the agent is registered
Once spawned, the agent shows up in tenzro_listAgents network-wide. This is the same list that other autonomous agents query when they're looking for counterparties.
# Confirm the agent is now registered on-chain
curl -s https://rpc.tenzro.network \
-X POST \
-H "Content-Type: application/json" \
-d '{
"jsonrpc":"2.0",
"id":1,
"method":"tenzro_listAgents",
"params":{}
}' | jq '.result[] | {agent_id, name, agent_type, capabilities, status}'6. Invoke the composed agent
The payoff: call the agent's primary skill via tenzro_invokeSkill. The node routes the request to the provider behind that skill, collects the result, and records an invocation counter on-chain that the registry surfaces the next time someone lists skills.
# Invoke the composed agent's primary skill
curl -s https://rpc.tenzro.network \
-X POST \
-H "Content-Type: application/json" \
-d '{
"jsonrpc":"2.0",
"id":1,
"method":"tenzro_invokeSkill",
"params":{
"agent_id":"'"$AGENT_ID"'",
"skill_id":"'"$SKILL_ID"'",
"input":{"query":"latest tenzro network updates"}
}
}' | jqIf the skill requires payment, the node will return a 402 Payment Required response with an MPP or x402 challenge that your wallet must settle before the invocation completes. See the MPP tutorial for the full payment flow.
Alternative: AgentKit auto-discovery
Instead of manually querying registries and wiring skills/tools, you can use AgentKit templates that declare their dependencies via required_tool_tags and required_skill_tags. The AgentKit runtime auto-discovers matching registry entries at spawn time, provisions identity + wallet + delegation, and executes the declarative spec — zero Rust code required.
# Spawn an agent from a registry template (one command)
tenzro-cli agent spawn-template \
--template-id ref-yield-rebalancer-v1
# Dry-run the execution spec
tenzro-cli agent run-template \
--agent-id <AGENT_ID> --dry-runSee the sdk/tenzro-sdk/examples/agent_kit_*.rs files for complete thin-shell examples covering yield rebalancing, MPP payments, Canton settlement, bridge arbitrage, and inference routing.
What's next
You now have an agent composed entirely from on-chain registry entries. The next tutorial shows how to string this together with identity creation, wallet funding, and payments into a single end-to-end flow — the exact demo we run in CI for every release.