Build a Custom Agent
If the built-in agents don't cover your domain, you can build and deploy your own agent using the Ariftly Node.js SDK. A custom agent is a Fastify server that implements the Remote Agent Protocol (RAP v1).
Prerequisites
- Node.js 20+
- An Ariftly account with a registered tenant
ARIFTLY_API_KEYin your environment
Scaffold a new agent
# In the Ariftly monorepo (for platform contributors)
pnpm agent:new my-agent
# Standalone (your own repo)
npx @ariftly/create-agent my-agent
cd my-agent
npm install
The scaffold creates:
my-agent/
├── src/
│ ├── index.ts # Fastify server with all RAP v1 routes
│ ├── manifest.ts # Agent capabilities declaration
│ ├── ai/prompts.ts # Your domain-specific prompts
│ └── tools/usage.ts # Which tools your agent needs
├── package.json
└── Dockerfile
The manifest
Every agent must declare its capabilities in manifest.ts:
import type { AgentManifest } from '@ariftly/agent-wire';
export const manifest: AgentManifest = {
agent_id: 'my-agent',
name: 'My Domain Agent',
description: 'What this agent does in one sentence',
version: '1.0.0',
wire_version: '1',
capabilities: ['task', 'invoke', 'session'],
task_types: [
{
type: 'my_domain.analyze',
description: 'Analyze something in my domain',
input_schema: {
type: 'object',
properties: {
target: { type: 'string' },
},
required: ['target'],
},
},
],
required_tools: ['github.repo.read', 'slack.message.send'],
required_scopes: ['github:read', 'slack:write'],
};
Handling a task
In src/index.ts, implement the task handler:
import { createAgentHandlers } from '@ariftly/agent-sdk-node';
import { manifest } from './manifest';
const handlers = createAgentHandlers({
manifest,
async onTask(trigger, emit) {
const { task_id, task_type, input } = trigger;
// Emit progress
await emit({ type: 'task.progress', task_id, message: 'Starting analysis' });
// Use the AI gateway (BYOK — uses the tenant's API key)
const { aiGateway } = trigger.credentials;
const result = await callAI(aiGateway, input.target);
// Use a tool via the proxy
const slackResult = await toolProxy.call(
trigger.tool_proxy,
'slack.message.send',
{ channel: '#alerts', text: result.summary }
);
// Emit completion with artifact
await emit({
type: 'task.complete',
task_id,
artifact: {
type: 'my_domain.analysis',
data: result,
},
});
},
});
RAP v1 routes
The SDK wires up all required routes automatically:
| Route | Handler |
|---|---|
GET /v1/manifest | Returns your manifest JSON |
GET /v1/health | Returns liveness + build sha |
POST /v1/task | Calls your onTask handler |
POST /v1/invoke | Calls your onInvoke handler |
POST /v1/session/:id/message | Calls your onMessage handler |
POST /v1/approval/:id/resolve | Calls your onApprovalResolved handler |
Deploying your agent
Deploy anywhere that can run a Node.js HTTP server:
# Vercel (recommended)
vercel deploy
# Docker
docker build -t my-agent .
docker run -p 3000:3000 my-agent
# Railway, Fly.io, etc.
# Just expose port 3000
Registering with Ariftly Core
Once deployed, register your agent's URL:
curl -X POST https://api.ariftly.io/v1/agent-registry \
-H "Authorization: Bearer $ARIFTLY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"agent_url": "https://my-agent.vercel.app",
"name": "My Domain Agent"
}'
Core will fetch your manifest, issue a signing keypair, and your agent is live.
Next steps
- RAP v1 Protocol — full protocol specification
- Tasks and Artifacts — how task I/O works
- Approvals and Skills — add human-in-the-loop gates