Skip to main content

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_KEY in 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:

RouteHandler
GET /v1/manifestReturns your manifest JSON
GET /v1/healthReturns liveness + build sha
POST /v1/taskCalls your onTask handler
POST /v1/invokeCalls your onInvoke handler
POST /v1/session/:id/messageCalls your onMessage handler
POST /v1/approval/:id/resolveCalls 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