Agent Tools
Tools are functions that agents can invoke to interact with external systems, retrieve data, or perform actions. This guide covers how to create, configure, and manage tools.
Tool Anatomy
Every tool has these components:
- TypeScript
- Java
- Go
- .NET
{
tag: 'tool-identifier', // Unique identifier
name: 'Human Readable Name', // Optional display name
description: 'What this tool does', // LLM uses this to decide when to call
parameters: { // Input schema
param1: {
type: 'string',
description: 'Parameter description',
required: true,
},
},
handler: async (ctx, params) => { // Implementation
return result;
},
requiresConfirmation: false, // Needs human approval?
timeout: 30000, // Timeout in ms
retries: 2, // Retry on failure
costEstimate: 0.01, // Estimated cost per call
}
Map.of(
"tag", "tool-identifier", // Unique identifier
"name", "Human Readable Name", // Optional display name
"description", "What this tool does", // LLM uses this to decide when to call
parameters: Map.of( // Input schema
param1: Map.of(
"type", "string",
"description", "Parameter description",
"required", true
)
),
handler: async (ctx, params) => Map.of( // Implementation
return result;
),
"requiresConfirmation", false, // Needs human approval?
"timeout", 30000, // Timeout in ms
"retries", 2, // Retry on failure
"costEstimate", 0.01, // Estimated cost per call
)
import "context"
{
"tag": "tool-identifier", // Unique identifier
"name": "Human Readable Name", // Optional display name
"description": "What this tool does", // LLM uses this to decide when to call
parameters: { // Input schema
param1: {
"type": "string",
"description": "Parameter description",
"required": true,
},
},
handler: async (ctx, params) => { // Implementation
return result;
},
"requiresConfirmation": false, // Needs human approval?
"timeout": 30000, // Timeout in ms
"retries": 2, // Retry on failure
"costEstimate": 0.01, // Estimated cost per call
}
{
["tag"] = "tool-identifier", // Unique identifier
["name"] = "Human Readable Name", // Optional display name
["description"] = "What this tool does", // LLM uses this to decide when to call
parameters: { // Input schema
param1: {
["type"] = "string",
["description"] = "Parameter description",
["required"] = true,
},
},
handler: async (ctx, params) => { // Implementation
return result;
},
["requiresConfirmation"] = false, // Needs human approval?
["timeout"] = 30000, // Timeout in ms
["retries"] = 2, // Retry on failure
["costEstimate"] = 0.01, // Estimated cost per call
}
Parameter Types
String
- TypeScript
- Java
- Go
- .NET
parameters: {
name: {
type: 'string',
description: 'User name',
required: true,
},
email: {
type: 'string',
description: 'Email address',
required: true,
},
}
parameters: Map.of(
name: Map.of(
"type", "string",
"description", "User name",
"required", true
),
email: Map.of(
"type", "string",
"description", "Email address",
"required", true
)
)
parameters: {
name: {
"type": "string",
"description": "User name",
"required": true,
},
email: {
"type": "string",
"description": "Email address",
"required": true,
},
}
parameters: {
name: {
["type"] = "string",
["description"] = "User name",
["required"] = true,
},
email: {
["type"] = "string",
["description"] = "Email address",
["required"] = true,
},
}
Number
- TypeScript
- Java
- Go
- .NET
parameters: {
amount: {
type: 'number',
description: 'Amount in dollars',
required: true,
},
quantity: {
type: 'number',
description: 'Number of items',
default: 1,
},
}
parameters: Map.of(
amount: Map.of(
"type", "number",
"description", "Amount in dollars",
"required", true
),
quantity: Map.of(
"type", "number",
"description", "Number of items",
"default", 1
)
)
parameters: {
amount: {
"type": "number",
"description": "Amount in dollars",
"required": true,
},
quantity: {
"type": "number",
"description": "Number of items",
"default": 1,
},
}
parameters: {
amount: {
["type"] = "number",
["description"] = "Amount in dollars",
["required"] = true,
},
quantity: {
["type"] = "number",
["description"] = "Number of items",
["default"] = 1,
},
}
Boolean
- TypeScript
- Java
- Go
- .NET
parameters: {
includeArchived: {
type: 'boolean',
description: 'Include archived items in results',
default: false,
},
}
parameters: Map.of(
includeArchived: Map.of(
"type", "boolean",
"description", "Include archived items in results",
"default", false
)
)
parameters: {
includeArchived: {
"type": "boolean",
"description": "Include archived items in results",
"default": false,
},
}
parameters: {
includeArchived: {
["type"] = "boolean",
["description"] = "Include archived items in results",
["default"] = false,
},
}
Enum (Constrained Values)
- TypeScript
- Java
- Go
- .NET
parameters: {
status: {
type: 'string',
description: 'Order status',
enum: ['pending', 'processing', 'shipped', 'delivered'],
required: true,
},
priority: {
type: 'string',
description: 'Priority level',
enum: ['low', 'medium', 'high', 'urgent'],
default: 'medium',
},
}
parameters: Map.of(
status: Map.of(
"type", "string",
"description", "Order status",
enum: ['pending', 'processing', 'shipped', 'delivered'],
"required", true
),
priority: Map.of(
"type", "string",
"description", "Priority level",
enum: ['low', 'medium', 'high', 'urgent'],
"default", "medium"
)
)
parameters: {
status: {
"type": "string",
"description": "Order status",
enum: ['pending', 'processing', 'shipped', 'delivered'],
"required": true,
},
priority: {
"type": "string",
"description": "Priority level",
enum: ['low', 'medium', 'high', 'urgent'],
"default": "medium",
},
}
parameters: {
status: {
["type"] = "string",
["description"] = "Order status",
enum: ['pending', 'processing', 'shipped', 'delivered'],
["required"] = true,
},
priority: {
["type"] = "string",
["description"] = "Priority level",
enum: ['low', 'medium', 'high', 'urgent'],
["default"] = "medium",
},
}
Array
- TypeScript
- Java
- Go
- .NET
parameters: {
tags: {
type: 'array',
description: 'List of tags to apply',
items: { type: 'string' },
},
productIds: {
type: 'array',
description: 'List of product IDs',
items: { type: 'number' },
required: true,
},
}
parameters: Map.of(
tags: Map.of(
"type", "array",
"description", "List of tags to apply",
items: Map.of( "type", "string" )
),
productIds: Map.of(
"type", "array",
"description", "List of product IDs",
items: Map.of( "type", "number" ),
"required", true
)
)
parameters: {
tags: {
"type": "array",
"description": "List of tags to apply",
items: { "type": "string" },
},
productIds: {
"type": "array",
"description": "List of product IDs",
items: { "type": "number" },
"required": true,
},
}
parameters: {
tags: {
["type"] = "array",
["description"] = "List of tags to apply",
items: { ["type"] = "string" },
},
productIds: {
["type"] = "array",
["description"] = "List of product IDs",
items: { ["type"] = "number" },
["required"] = true,
},
}
Object
- TypeScript
- Java
- Go
- .NET
parameters: {
address: {
type: 'object',
description: 'Shipping address',
properties: {
street: { type: 'string', description: 'Street address' },
city: { type: 'string', description: 'City' },
state: { type: 'string', description: 'State/Province' },
zip: { type: 'string', description: 'ZIP/Postal code' },
country: { type: 'string', description: 'Country' },
},
required: true,
},
}
parameters: Map.of(
address: Map.of(
"type", "object",
"description", "Shipping address",
properties: Map.of(
street: Map.of( "type", "string", "description", "Street address" ),
city: Map.of( "type", "string", "description", "City" ),
state: Map.of( "type", "string", "description", "State/Province" ),
zip: Map.of( "type", "string", "description", "ZIP/Postal code" ),
country: Map.of( "type", "string", "description", "Country" )
),
"required", true
)
)
parameters: {
address: {
"type": "object",
"description": "Shipping address",
properties: {
street: { "type": "string", "description": "Street address" },
city: { "type": "string", "description": "City" },
state: { "type": "string", "description": "State/Province" },
zip: { "type": "string", "description": "ZIP/Postal code" },
country: { "type": "string", "description": "Country" },
},
"required": true,
},
}
parameters: {
address: {
["type"] = "object",
["description"] = "Shipping address",
properties: {
street: { ["type"] = "string", ["description"] = "Street address" },
city: { ["type"] = "string", ["description"] = "City" },
state: { ["type"] = "string", ["description"] = "State/Province" },
zip: { ["type"] = "string", ["description"] = "ZIP/Postal code" },
country: { ["type"] = "string", ["description"] = "Country" },
},
["required"] = true,
},
}
Tool Handler Context
The handler receives a context object with access to Ductape resources:
- TypeScript
- Java
- Go
- .NET
handler: async (ctx, params) => {
// Execution metadata
ctx.executionId; // Unique execution ID
ctx.agentTag; // Agent tag
ctx.env; // Environment (dev, staging, prd)
ctx.product; // Product tag
ctx.iteration; // Current iteration number
ctx.sessionId; // Session ID for memory
// Input data
ctx.input; // Original user input
// History
ctx.conversationHistory; // Message history
ctx.toolCallHistory; // Previous tool calls
// Custom state
ctx.state; // Agent state object
ctx.setState(key, value);
ctx.getState(key);
// Ductape resources (see below)
ctx.action;
ctx.database;
ctx.graph;
ctx.storage;
ctx.notification;
ctx.publish;
ctx.feature;
// Memory operations
ctx.remember(data); // Store in vector memory
ctx.recall(query); // Query vector memory
}
handler: async (ctx, params) => Map.of(
// Execution metadata
ctx.executionId; // Unique execution ID
ctx.agentTag; // Agent tag
ctx.env; // Environment (dev, staging, prd)
ctx.product; // Product tag
ctx.iteration; // Current iteration number
ctx.sessionId; // Session ID for memory
// Input data
ctx.input; // Original user input
// History
ctx.conversationHistory; // Message history
ctx.toolCallHistory; // Previous tool calls
// Custom state
ctx.state; // Agent state object
ctx.setState(key, value);
ctx.getState(key);
// Ductape resources (see below)
ctx.action;
ctx.database;
ctx.graph;
ctx.storage;
ctx.notification;
ctx.publish;
ctx.feature;
// Memory operations
ctx.remember(data); // Store in vector memory
ctx.recall(query); // Query vector memory
)
import "context"
handler: async (ctx, params) => {
// Execution metadata
ctx.executionId; // Unique execution ID
ctx.agentTag; // Agent tag
ctx.env; // Environment (dev, staging, prd)
ctx.product; // Product tag
ctx.iteration; // Current iteration number
ctx.sessionId; // Session ID for memory
// Input data
ctx.input; // Original user input
// History
ctx.conversationHistory; // Message history
ctx.toolCallHistory; // Previous tool calls
// Custom state
ctx.state; // Agent state object
ctx.setState(key, value);
ctx.getState(key);
// Ductape resources (see below)
ctx.action;
ctx.database;
ctx.graph;
ctx.storage;
ctx.notification;
ctx.publish;
ctx.feature;
// Memory operations
ctx.remember(data); // Store in vector memory
ctx.recall(query); // Query vector memory
}
handler: async (ctx, params) => {
// Execution metadata
ctx.executionId; // Unique execution ID
ctx.agentTag; // Agent tag
ctx.env; // Environment (dev, staging, prd)
ctx.product; // Product tag
ctx.iteration; // Current iteration number
ctx.sessionId; // Session ID for memory
// Input data
ctx.input; // Original user input
// History
ctx.conversationHistory; // Message history
ctx.toolCallHistory; // Previous tool calls
// Custom state
ctx.state; // Agent state object
ctx.setState(key, value);
ctx.getState(key);
// Ductape resources (see below)
ctx.action;
ctx.database;
ctx.graph;
ctx.storage;
ctx.notification;
ctx.publish;
ctx.feature;
// Memory operations
ctx.remember(data); // Store in vector memory
ctx.recall(query); // Query vector memory
}
Accessing Ductape Resources
Actions (External APIs)
- TypeScript
- Java
- Go
- .NET
handler: async (ctx, params) => {
const response = await ctx.api.run({
app: 'stripe',
event: 'create-charge',
input: {
amount: params.amount * 100,
currency: 'usd',
customer: params.customerId
},
retries: 3,
timeout: 10000,
});
return { chargeId: response.id, status: response.status };
}
handler: async (ctx, params) => Map.of(
Map<String, Object> response = ctx.api.run(Map.of(
"app", "stripe",
"event", "create-charge",
input: Map.of(
amount: params.amount * 100,
"currency", "usd",
customer: params.customerId
),
"retries", 3,
"timeout", 10000
));
return Map.of( chargeId: response.id, status: response.status );
)
import "context"
handler: async (ctx, params) => {
response := ctx.api.run({
"app": "stripe",
"event": "create-charge",
input: {
amount: params.amount * 100,
"currency": "usd",
customer: params.customerId
},
"retries": 3,
"timeout": 10000,
});
return { chargeId: response.id, status: response.status };
}
handler: async (ctx, params) => {
var response = await ctx.api.run({
["app"] = "stripe",
["event"] = "create-charge",
input: {
amount: params.amount * 100,
["currency"] = "usd",
customer: params.customerId
},
["retries"] = 3,
["timeout"] = 10000,
});
return { chargeId: response.id, status: response.status };
}
The input uses flat format - fields are automatically resolved to body, params, query, or headers based on the action's schema. For explicit placement, use prefixes like 'headers:X-Custom': 'value'.
Databases
- TypeScript
- Java
- Go
- .NET
handler: async (ctx, params) => {
// Query
const users = await ctx.database.query({
database: 'users-db',
event: 'find-users',
params: { status: 'active' },
});
// Insert
const newUser = await ctx.database.insert({
database: 'users-db',
event: 'create-user',
data: { email: params.email, name: params.name },
});
// Update
await ctx.database.update({
database: 'users-db',
event: 'update-user',
where: { id: params.userId },
data: { status: 'verified' },
});
// Delete
await ctx.database.delete({
database: 'users-db',
event: 'delete-user',
where: { id: params.userId },
});
return { success: true };
}
handler: async (ctx, params) => Map.of(
// Query
Map<String, Object> users = ctx.database.query(Map.of(
"database", "users-db",
"event", "find-users",
params: Map.of( "status", "active" )
));
// Insert
Map<String, Object> newUser = ctx.database.insert(Map.of(
"database", "users-db",
"event", "create-user",
data: Map.of( email: params.email, name: params.name )
));
// Update
ctx.database.update(Map.of(
"database", "users-db",
"event", "update-user",
where: Map.of( id: params.userId ),
data: Map.of( "status", "verified" )
));
// Delete
ctx.database.delete(Map.of(
"database", "users-db",
"event", "delete-user",
where: Map.of( id: params.userId )
));
return Map.of( "success", true );
)
import "context"
handler: async (ctx, params) => {
// Query
users := ctx.database.query({
"database": "users-db",
"event": "find-users",
params: { "status": "active" },
});
// Insert
newUser := ctx.database.insert({
"database": "users-db",
"event": "create-user",
data: { email: params.email, name: params.name },
});
// Update
ctx.database.update({
"database": "users-db",
"event": "update-user",
where: { id: params.userId },
data: { "status": "verified" },
});
// Delete
ctx.database.delete({
"database": "users-db",
"event": "delete-user",
where: { id: params.userId },
});
return { "success": true };
}
handler: async (ctx, params) => {
// Query
var users = await ctx.database.query({
["database"] = "users-db",
["event"] = "find-users",
params: { ["status"] = "active" },
});
// Insert
var newUser = await ctx.database.insert({
["database"] = "users-db",
["event"] = "create-user",
data: { email: params.email, name: params.name },
});
// Update
await ctx.database.update({
["database"] = "users-db",
["event"] = "update-user",
where: { id: params.userId },
data: { ["status"] = "verified" },
});
// Delete
await ctx.database.delete({
["database"] = "users-db",
["event"] = "delete-user",
where: { id: params.userId },
});
return { ["success"] = true };
}
Graph Databases
- TypeScript
- Java
- Go
- .NET
handler: async (ctx, params) => {
// Create node
const node = await ctx.graph.createNode({
graph: 'social-graph',
labels: ['User'],
properties: { name: params.name },
});
// Create relationship
await ctx.graph.createRelationship({
graph: 'social-graph',
from: node.id,
to: params.friendId,
type: 'FRIENDS_WITH',
});
// Query
const friends = await ctx.graph.query({
graph: 'social-graph',
action: 'find-friends',
params: { userId: params.userId },
});
return { friends };
}
handler: async (ctx, params) => Map.of(
// Create node
Map<String, Object> node = ctx.graph.createNode(Map.of(
"graph", "social-graph",
labels: ['User'],
properties: Map.of( name: params.name )
));
// Create relationship
ctx.graph.createRelationship(Map.of(
"graph", "social-graph",
from: node.id,
to: params.friendId,
"type", "FRIENDS_WITH"
));
// Query
Map<String, Object> friends = ctx.graph.query(Map.of(
"graph", "social-graph",
"action", "find-friends",
params: Map.of( userId: params.userId )
));
return Map.of( friends );
)
import "context"
handler: async (ctx, params) => {
// Create node
node := ctx.graph.createNode({
"graph": "social-graph",
labels: ['User'],
properties: { name: params.name },
});
// Create relationship
ctx.graph.createRelationship({
"graph": "social-graph",
from: node.id,
to: params.friendId,
"type": "FRIENDS_WITH",
});
// Query
friends := ctx.graph.query({
"graph": "social-graph",
"action": "find-friends",
params: { userId: params.userId },
});
return { friends };
}
handler: async (ctx, params) => {
// Create node
var node = await ctx.graph.createNode({
["graph"] = "social-graph",
labels: ['User'],
properties: { name: params.name },
});
// Create relationship
await ctx.graph.createRelationship({
["graph"] = "social-graph",
from: node.id,
to: params.friendId,
["type"] = "FRIENDS_WITH",
});
// Query
var friends = await ctx.graph.query({
["graph"] = "social-graph",
["action"] = "find-friends",
params: { userId: params.userId },
});
return { friends };
}
Storage
- TypeScript
- Java
- Go
- .NET
handler: async (ctx, params) => {
// Upload
const file = await ctx.storage.upload({
storage: 'documents',
event: 'upload-file',
input: {
buffer: params.fileData,
fileName: params.fileName,
mimeType: params.mimeType,
},
});
// Download
const download = await ctx.storage.download({
storage: 'documents',
event: 'get-file',
input: { file_key: params.fileKey },
});
// Delete
await ctx.storage.delete({
storage: 'documents',
event: 'remove-file',
input: { file_key: params.fileKey },
});
return { url: file.url };
}
handler: async (ctx, params) => Map.of(
// Upload
Map<String, Object> file = ctx.storage.upload(Map.of(
"storage", "documents",
"event", "upload-file",
input: Map.of(
buffer: params.fileData,
fileName: params.fileName,
mimeType: params.mimeType
)
));
// Download
Map<String, Object> download = ctx.storage.download(Map.of(
"storage", "documents",
"event", "get-file",
input: Map.of( file_key: params.fileKey )
));
// Delete
ctx.storage.delete(Map.of(
"storage", "documents",
"event", "remove-file",
input: Map.of( file_key: params.fileKey )
));
return Map.of( url: file.url );
)
import "context"
handler: async (ctx, params) => {
// Upload
file := ctx.storage.upload({
"storage": "documents",
"event": "upload-file",
input: {
buffer: params.fileData,
fileName: params.fileName,
mimeType: params.mimeType,
},
});
// Download
download := ctx.storage.download({
"storage": "documents",
"event": "get-file",
input: { file_key: params.fileKey },
});
// Delete
ctx.storage.delete({
"storage": "documents",
"event": "remove-file",
input: { file_key: params.fileKey },
});
return { url: file.url };
}
handler: async (ctx, params) => {
// Upload
var file = await ctx.storage.upload({
["storage"] = "documents",
["event"] = "upload-file",
input: {
buffer: params.fileData,
fileName: params.fileName,
mimeType: params.mimeType,
},
});
// Download
var download = await ctx.storage.download({
["storage"] = "documents",
["event"] = "get-file",
input: { file_key: params.fileKey },
});
// Delete
await ctx.storage.delete({
["storage"] = "documents",
["event"] = "remove-file",
input: { file_key: params.fileKey },
});
return { url: file.url };
}
Notifications
- TypeScript
- Java
- Go
- .NET
handler: async (ctx, params) => {
// Email
await ctx.notification.email({
notification: 'transactional',
event: 'order-confirmation',
recipients: [params.email],
subject: { orderId: params.orderId },
template: { orderId: params.orderId, items: params.items },
});
// SMS
await ctx.notification.sms({
notification: 'alerts',
event: 'shipping-update',
phones: [params.phone],
message: { trackingNumber: params.tracking },
});
// Push
await ctx.notification.push({
notification: 'mobile',
event: 'new-message',
tokens: [params.deviceToken],
title: { sender: params.senderName },
body: { preview: params.messagePreview },
});
return { sent: true };
}
handler: async (ctx, params) => Map.of(
// Email
ctx.notification.email(Map.of(
"notification", "transactional",
"event", "order-confirmation",
recipients: [params.email],
subject: Map.of( orderId: params.orderId ),
template: Map.of( orderId: params.orderId, items: params.items )
));
// SMS
ctx.notification.sms(Map.of(
"notification", "alerts",
"event", "shipping-update",
phones: [params.phone],
message: Map.of( trackingNumber: params.tracking )
));
// Push
ctx.notification.push(Map.of(
"notification", "mobile",
"event", "new-message",
tokens: [params.deviceToken],
title: Map.of( sender: params.senderName ),
body: Map.of( preview: params.messagePreview )
));
return Map.of( "sent", true );
)
import "context"
handler: async (ctx, params) => {
// Email
ctx.notification.email({
"notification": "transactional",
"event": "order-confirmation",
recipients: [params.email],
subject: { orderId: params.orderId },
template: { orderId: params.orderId, items: params.items },
});
// SMS
ctx.notification.sms({
"notification": "alerts",
"event": "shipping-update",
phones: [params.phone],
message: { trackingNumber: params.tracking },
});
// Push
ctx.notification.push({
"notification": "mobile",
"event": "new-message",
tokens: [params.deviceToken],
title: { sender: params.senderName },
body: { preview: params.messagePreview },
});
return { "sent": true };
}
handler: async (ctx, params) => {
// Email
await ctx.notification.email({
["notification"] = "transactional",
["event"] = "order-confirmation",
recipients: [params.email],
subject: { orderId: params.orderId },
template: { orderId: params.orderId, items: params.items },
});
// SMS
await ctx.notification.sms({
["notification"] = "alerts",
["event"] = "shipping-update",
phones: [params.phone],
message: { trackingNumber: params.tracking },
});
// Push
await ctx.notification.push({
["notification"] = "mobile",
["event"] = "new-message",
tokens: [params.deviceToken],
title: { sender: params.senderName },
body: { preview: params.messagePreview },
});
return { ["sent"] = true };
}
Message Publishing
- TypeScript
- Java
- Go
- .NET
handler: async (ctx, params) => {
await ctx.messaging.produce({
event: 'order-events:order-created',
message: {
orderId: params.orderId,
customerId: params.customerId,
items: params.items,
},
});
return { published: true };
}
handler: async (ctx, params) => Map.of(
ctx.messaging.produce(Map.of(
"event", "order-events:order-created",
message: Map.of(
orderId: params.orderId,
customerId: params.customerId,
items: params.items
)
));
return Map.of( "published", true );
)
import "context"
handler: async (ctx, params) => {
ctx.messaging.produce({
"event": "order-events:order-created",
message: {
orderId: params.orderId,
customerId: params.customerId,
items: params.items,
},
});
return { "published": true };
}
handler: async (ctx, params) => {
await ctx.messaging.produce({
["event"] = "order-events:order-created",
message: {
orderId: params.orderId,
customerId: params.customerId,
items: params.items,
},
});
return { ["published"] = true };
}
Features (Features)
- TypeScript
- Java
- Go
- .NET
handler: async (ctx, params) => {
const result = await ctx.feature.run({
feature: 'process-order',
input: {
orderId: params.orderId,
action: 'fulfill',
},
});
return result;
}
handler: async (ctx, params) => Map.of(
Map<String, Object> result = ctx.feature.run(Map.of(
"feature", "process-order",
input: Map.of(
orderId: params.orderId,
"action", "fulfill"
)
));
return result;
)
import "context"
handler: async (ctx, params) => {
result := ctx.feature.run({
"feature": "process-order",
input: {
orderId: params.orderId,
"action": "fulfill",
},
});
return result;
}
handler: async (ctx, params) => {
var result = await ctx.feature.run({
["feature"] = "process-order",
input: {
orderId: params.orderId,
["action"] = "fulfill",
},
});
return result;
}
Vector Memory Operations
Remember (Store)
- TypeScript
- Java
- Go
- .NET
handler: async (ctx, params) => {
await ctx.remember({
content: params.information,
metadata: {
type: 'user_preference',
userId: ctx.sessionId,
category: params.category,
timestamp: new Date().toISOString(),
},
});
return { stored: true };
}
handler: async (ctx, params) => Map.of(
ctx.remember(Map.of(
content: params.information,
metadata: Map.of(
"type", "user_preference",
userId: ctx.sessionId,
category: params.category,
timestamp: Instant.now().toISOString()
)
));
return Map.of( "stored", true );
)
import "context"
handler: async (ctx, params) => {
ctx.remember({
content: params.information,
metadata: {
"type": "user_preference",
userId: ctx.sessionId,
category: params.category,
timestamp: new Date().toISOString(),
},
});
return { "stored": true };
}
handler: async (ctx, params) => {
await ctx.remember({
content: params.information,
metadata: {
["type"] = "user_preference",
userId: ctx.sessionId,
category: params.category,
timestamp: DateTime.UtcNow.toISOString(),
},
});
return { ["stored"] = true };
}
Recall (Query)
- TypeScript
- Java
- Go
- .NET
handler: async (ctx, params) => {
const memories = await ctx.recall({
query: params.query,
topK: 5,
filter: {
userId: ctx.sessionId,
type: 'user_preference',
},
minScore: 0.7,
});
return {
found: memories.matches.length,
results: memories.matches.map((m) => m.metadata),
};
}
handler: async (ctx, params) => Map.of(
Map<String, Object> memories = ctx.recall(Map.of(
query: params.query,
"topK", 5,
filter: Map.of(
userId: ctx.sessionId,
"type", "user_preference"
),
"minScore", 0.7
));
return Map.of(
found: memories.matches.length,
results: memories.matches.map((m) => m.metadata)
);
)
import "context"
handler: async (ctx, params) => {
memories := ctx.recall({
query: params.query,
"topK": 5,
filter: {
userId: ctx.sessionId,
"type": "user_preference",
},
"minScore": 0.7,
});
return {
found: memories.matches.length,
results: memories.matches.map((m) => m.metadata),
};
}
handler: async (ctx, params) => {
var memories = await ctx.recall({
query: params.query,
["topK"] = 5,
filter: {
userId: ctx.sessionId,
["type"] = "user_preference",
},
["minScore"] = 0.7,
});
return {
found: memories.matches.length,
results: memories.matches.map((m) => m.metadata),
};
}
Portable Tools with handlerRef
For tools that should work when loaded from the database, use handlerRef instead of inline handlers:
- TypeScript
- Java
- Go
- .NET
tools: [
{
tag: 'process-payment',
description: 'Process a payment',
parameters: {
amount: { type: 'number', required: true },
customerId: { type: 'string', required: true },
},
handlerRef: 'action:stripe:create-charge',
},
{
tag: 'get-customer',
description: 'Look up customer information',
parameters: {
customerId: { type: 'string', required: true },
},
handlerRef: 'database:customers-db:find-customer',
},
{
tag: 'run-checkout',
description: 'Run checkout feature',
parameters: {
cartId: { type: 'string', required: true },
},
handlerRef: 'feature:checkout-flow',
},
]
tools: [
Map.of(
"tag", "process-payment",
"description", "Process a payment",
parameters: Map.of(
amount: Map.of( "type", "number", "required", true ),
customerId: Map.of( "type", "string", "required", true )
),
"handlerRef", "action:stripe:create-charge"
),
Map.of(
"tag", "get-customer",
"description", "Look up customer information",
parameters: Map.of(
customerId: Map.of( "type", "string", "required", true )
),
"handlerRef", "database:customers-db:find-customer"
),
Map.of(
"tag", "run-checkout",
"description", "Run checkout feature",
parameters: Map.of(
cartId: Map.of( "type", "string", "required", true )
),
"handlerRef", "feature:checkout-flow"
),
]
tools: [
{
"tag": "process-payment",
"description": "Process a payment",
parameters: {
amount: { "type": "number", "required": true },
customerId: { "type": "string", "required": true },
},
"handlerRef": "action:stripe:create-charge",
},
{
"tag": "get-customer",
"description": "Look up customer information",
parameters: {
customerId: { "type": "string", "required": true },
},
"handlerRef": "database:customers-db:find-customer",
},
{
"tag": "run-checkout",
"description": "Run checkout feature",
parameters: {
cartId: { "type": "string", "required": true },
},
"handlerRef": "feature:checkout-flow",
},
]
tools: [
{
["tag"] = "process-payment",
["description"] = "Process a payment",
parameters: {
amount: { ["type"] = "number", ["required"] = true },
customerId: { ["type"] = "string", ["required"] = true },
},
["handlerRef"] = "action:stripe:create-charge",
},
{
["tag"] = "get-customer",
["description"] = "Look up customer information",
parameters: {
customerId: { ["type"] = "string", ["required"] = true },
},
["handlerRef"] = "database:customers-db:find-customer",
},
{
["tag"] = "run-checkout",
["description"] = "Run checkout feature",
parameters: {
cartId: { ["type"] = "string", ["required"] = true },
},
["handlerRef"] = "feature:checkout-flow",
},
]
handlerRef Format
type:tag:event
| Type | Format | Description |
|---|---|---|
action | action:app-tag:event | Call an app action |
database | database:db-tag:event | Database query |
graph | graph:graph-tag:action | Graph operation |
storage | storage:storage-tag:event | Storage operation |
notification | notification:notif-tag:event | Send notification |
publish | publish:broker-tag:event | Publish message |
feature | feature:feature-tag | Run feature |
Tool Options
Timeout
Set maximum execution time:
- TypeScript
- Java
- Go
- .NET
{
tag: 'slow-operation',
description: 'A slow operation',
timeout: 60000, // 60 seconds
handler: async (ctx, params) => {
// Long-running operation
},
}
Map.of(
"tag", "slow-operation",
"description", "A slow operation",
"timeout", 60000, // 60 seconds
handler: async (ctx, params) => Map.of(
// Long-running operation
)
)
import "context"
{
"tag": "slow-operation",
"description": "A slow operation",
"timeout": 60000, // 60 seconds
handler: async (ctx, params) => {
// Long-running operation
},
}
{
["tag"] = "slow-operation",
["description"] = "A slow operation",
["timeout"] = 60000, // 60 seconds
handler: async (ctx, params) => {
// Long-running operation
},
}
Retries
Automatically retry on failure:
- TypeScript
- Java
- Go
- .NET
{
tag: 'unreliable-api',
description: 'Call an unreliable API',
retries: 3, // Retry up to 3 times
handler: async (ctx, params) => {
// May fail occasionally
},
}
Map.of(
"tag", "unreliable-api",
"description", "Call an unreliable API",
"retries", 3, // Retry up to 3 times
handler: async (ctx, params) => Map.of(
// May fail occasionally
)
)
import "context"
{
"tag": "unreliable-api",
"description": "Call an unreliable API",
"retries": 3, // Retry up to 3 times
handler: async (ctx, params) => {
// May fail occasionally
},
}
{
["tag"] = "unreliable-api",
["description"] = "Call an unreliable API",
["retries"] = 3, // Retry up to 3 times
handler: async (ctx, params) => {
// May fail occasionally
},
}
Human Approval
Require confirmation before execution:
- TypeScript
- Java
- Go
- .NET
{
tag: 'delete-account',
description: 'Permanently delete a user account',
requiresConfirmation: true,
handler: async (ctx, params) => {
// Destructive operation
},
}
Map.of(
"tag", "delete-account",
"description", "Permanently delete a user account",
"requiresConfirmation", true,
handler: async (ctx, params) => Map.of(
// Destructive operation
)
)
import "context"
{
"tag": "delete-account",
"description": "Permanently delete a user account",
"requiresConfirmation": true,
handler: async (ctx, params) => {
// Destructive operation
},
}
{
["tag"] = "delete-account",
["description"] = "Permanently delete a user account",
["requiresConfirmation"] = true,
handler: async (ctx, params) => {
// Destructive operation
},
}
Cost Estimation
Track estimated costs:
- TypeScript
- Java
- Go
- .NET
{
tag: 'expensive-operation',
description: 'An operation that costs money',
costEstimate: 0.10, // $0.10 per call
handler: async (ctx, params) => {
// Paid API call
},
}
Map.of(
"tag", "expensive-operation",
"description", "An operation that costs money",
"costEstimate", 0.10, // $0.10 per call
handler: async (ctx, params) => Map.of(
// Paid API call
)
)
import "context"
{
"tag": "expensive-operation",
"description": "An operation that costs money",
"costEstimate": 0.10, // $0.10 per call
handler: async (ctx, params) => {
// Paid API call
},
}
{
["tag"] = "expensive-operation",
["description"] = "An operation that costs money",
["costEstimate"] = 0.10, // $0.10 per call
handler: async (ctx, params) => {
// Paid API call
},
}
Best Practices
1. Write Clear Descriptions
The LLM uses descriptions to decide when to use tools:
- TypeScript
- Java
- Go
- .NET
// Good - specific and actionable
description: 'Search for products by name, category, or price range. Returns matching products with prices and availability.'
// Bad - vague
description: 'Search products'
// Good - specific and actionable
"description", "Search for products by name, category, or price range. Returns matching products with prices and availability."
// Bad - vague
"description", "Search products"
// Good - specific and actionable
"description": "Search for products by name, category, or price range. Returns matching products with prices and availability."
// Bad - vague
"description": "Search products"
// Good - specific and actionable
["description"] = "Search for products by name, category, or price range. Returns matching products with prices and availability."
// Bad - vague
["description"] = "Search products"
2. Use Descriptive Parameter Names
- TypeScript
- Java
- Go
- .NET
// Good
parameters: {
customerEmail: { type: 'string', description: 'Customer email address' },
orderDateFrom: { type: 'string', description: 'Start date for order search (YYYY-MM-DD)' },
}
// Bad
parameters: {
e: { type: 'string' },
d: { type: 'string' },
}
// Good
parameters: Map.of(
customerEmail: Map.of( "type", "string", "description", "Customer email address" ),
orderDateFrom: Map.of( "type", "string", "description", "Start date for order search (YYYY-MM-DD)" )
)
// Bad
parameters: Map.of(
e: Map.of( "type", "string" ),
d: Map.of( "type", "string" )
)
// Good
parameters: {
customerEmail: { "type": "string", "description": "Customer email address" },
orderDateFrom: { "type": "string", "description": "Start date for order search (YYYY-MM-DD)" },
}
// Bad
parameters: {
e: { "type": "string" },
d: { "type": "string" },
}
// Good
parameters: {
customerEmail: { ["type"] = "string", ["description"] = "Customer email address" },
orderDateFrom: { ["type"] = "string", ["description"] = "Start date for order search (YYYY-MM-DD)" },
}
// Bad
parameters: {
e: { ["type"] = "string" },
d: { ["type"] = "string" },
}
3. Return Structured Data
- TypeScript
- Java
- Go
- .NET
// Good - structured, informative
handler: async (ctx, params) => {
const order = await getOrder(params.orderId);
return {
found: true,
order: {
id: order.id,
status: order.status,
total: order.total,
items: order.items.length,
},
};
}
// Bad - unstructured
handler: async (ctx, params) => {
return await getOrder(params.orderId);
}
// Good - structured, informative
handler: async (ctx, params) => Map.of(
Map<String, Object> order = getOrder(params.orderId);
return Map.of(
"found", true,
order: Map.of(
id: order.id,
status: order.status,
total: order.total,
items: order.items.length
)
);
)
// Bad - unstructured
handler: async (ctx, params) => Map.of(
return getOrder(params.orderId);
)
import "context"
// Good - structured, informative
handler: async (ctx, params) => {
order := getOrder(params.orderId);
return {
"found": true,
order: {
id: order.id,
status: order.status,
total: order.total,
items: order.items.length,
},
};
}
// Bad - unstructured
handler: async (ctx, params) => {
return getOrder(params.orderId);
}
// Good - structured, informative
handler: async (ctx, params) => {
var order = await getOrder(params.orderId);
return {
["found"] = true,
order: {
id: order.id,
status: order.status,
total: order.total,
items: order.items.length,
},
};
}
// Bad - unstructured
handler: async (ctx, params) => {
return await getOrder(params.orderId);
}
4. Handle Errors Gracefully
- TypeScript
- Java
- Go
- .NET
handler: async (ctx, params) => {
try {
const result = await riskyOperation(params);
return { success: true, data: result };
} catch (error) {
return {
success: false,
error: error.message,
suggestion: 'Try with different parameters',
};
}
}
handler: async (ctx, params) => Map.of(
try Map.of(
Map<String, Object> result = riskyOperation(params);
return Map.of( "success", true, data: result );
) catch (error) Map.of(
return Map.of(
"success", false,
error: error.message,
"suggestion", "Try with different parameters"
);
)
)
import "context"
handler: async (ctx, params) => {
try {
result := riskyOperation(params);
return { "success": true, data: result };
} catch (error) {
return {
"success": false,
error: error.message,
"suggestion": "Try with different parameters",
};
}
}
handler: async (ctx, params) => {
try {
var result = await riskyOperation(params);
return { ["success"] = true, data: result };
} catch (error) {
return {
["success"] = false,
error: error.message,
["suggestion"] = "Try with different parameters",
};
}
}
5. Validate Inputs
- TypeScript
- Java
- Go
- .NET
handler: async (ctx, params) => {
// Validate email format
if (!isValidEmail(params.email)) {
return { error: 'Invalid email format' };
}
// Validate amount
if (params.amount <= 0) {
return { error: 'Amount must be positive' };
}
// Proceed with operation
return await processPayment(params);
}
handler: async (ctx, params) => Map.of(
// Validate email format
if (!isValidEmail(params.email)) Map.of(
return Map.of( "error", "Invalid email format" );
)
// Validate amount
if (params.amount <= 0) Map.of(
return Map.of( "error", "Amount must be positive" );
)
// Proceed with operation
return processPayment(params);
)
import "context"
handler: async (ctx, params) => {
// Validate email format
if (!isValidEmail(params.email)) {
return { "error": "Invalid email format" };
}
// Validate amount
if (params.amount <= 0) {
return { "error": "Amount must be positive" };
}
// Proceed with operation
return processPayment(params);
}
handler: async (ctx, params) => {
// Validate email format
if (!isValidEmail(params.email)) {
return { ["error"] = "Invalid email format" };
}
// Validate amount
if (params.amount <= 0) {
return { ["error"] = "Amount must be positive" };
}
// Proceed with operation
return await processPayment(params);
}
Next Steps
- Memory - Configure agent memory
- Human-in-the-Loop - Add approval features
- Examples - See tools in action