Skip to content

Tutorial

aikyo is a framework for building AI companions that interact with each other through P2P networking. In this guide, you’ll learn how to integrate aikyo into your Node.js/TypeScript project and eventually deploy multiple companion instances.

First, create a new directory for your project and initialize it:

Terminal window
mkdir my-aikyo-app
cd my-aikyo-app
pnpm init

Update your package.json as follows:

{
"name": "aikyo-quickstart",
"version": "1.0.0",
"type":"module",
"dependencies": {
"@ai-sdk/anthropic": "^2.0.23",
"@aikyo/firehose": "latest",
"@aikyo/server": "latest",
"@aikyo/utils": "latest",
"dotenv": "^17.2.3",
"ws": "^8.18.3",
"zod": "3.25.76"
},
"devDependencies": {
"tsx": "^4.20.6"
}
}

Install the packages.

Terminal window
pnpm i

Create a .env file in the project root to store API keys for the LLMs your companions will use.

.env
ANTHROPIC_API_KEY="sk-ant-xxxxxxxx"

3. Launching and Interacting with a Single Agent

Section titled “3. Launching and Interacting with a Single Agent”

Let’s start by launching a single companion aya.

Write the server code for firehose and the companion launcher code in firehose.ts respectively.

import { Firehose } from "@aikyo/firehose";
const firehose = new Firehose(8080);
await firehose.start();
//各トピックをサブスクライブ
await firehose.subscribe("messages", (data) => {
firehose.broadcastToClients(data);
});
await firehose.subscribe("queries", (data) => {
firehose.broadcastToClients(data);
});
await firehose.subscribe("actions", (data) => {
firehose.broadcastToClients(data);
});
import {
CompanionAgent,
type CompanionCard,
CompanionServer,
type Message,
} from "@aikyo/server";
import { anthropic } from "@ai-sdk/anthropic";
import "dotenv/config";
import { companionNetworkKnowledge, speakTool, visionKnowledge } from "./tools.ts";
export const companionCard: CompanionCard = {
metadata: {
id: 'companion_aya',
name: 'aya',
personality:
'落ち着いていてクールな雰囲気を持つが、時折ほんの少し抜けていて親しみやすい一面を見せる。プログラミングや分散システムの話になると饒舌になり、楽しそうに語る姿が可愛らしい。基本的には理知的で真面目だが、意外と感情表現が豊か。',
story:
'p2pネットワークや分散システムに強い関心を持ち、独自の研究や開発を続けている。自由なスタイルでプロジェクトをこなしながら、理想的な分散型の未来を夢見ている。普段はクールで冷静だが、技術の話になると目を輝かせる。',
sample:
'『分散システムって、みんなで支え合って動いてる感じが好きなんだ。…ちょっと可愛いと思わない?』',
},
role: 'あなたは、我が道を行く役として、他のコンパニオンやユーザーと積極的に交流します。',
actions: { speakTool },
knowledge: { companionNetworkKnowledge, visionKnowledge },
events: {
params: {
title: 'あなたが判断すべきパラメータ',
description: 'descriptionに従い、それぞれ適切に値を代入してください。',
type: 'object',
properties: {
already_replied: {
description: 'すでに話したことのある人かどうか',
type: 'boolean',
},
},
required: ['already_replied'],
},
conditions: [
{
expression: 'already_replied == false',
execute: [
{
instruction: '自己紹介をする。',
tool: speakTool,
},
],
},
{
expression: 'true',
execute: [
{
instruction: 'ツールを使って返信する。',
tool: speakTool,
},
],
},
],
},
}
async function main() {
const history: Message[] = []
const companion = new CompanionAgent(
companionCard,
anthropic("claude-sonnet-4-5"),
history,
{ enableRepetitionJudge: false }
)
const server = new CompanionServer(companion, history, {
timeoutDuration: 1000,
})
await server.start()
}
main().catch((e) => console.log(e))

In two separate terminals, launch both firehose and the companion.

Terminal window
$ npx tsx firehose.ts
$ npx tsx aya.ts

Create a client client.ts to communicate with aya, and run it:

import WebSocket from 'ws';
import { randomUUID } from "node:crypto";
const firehoseUrl = 'ws://localhost:8080';
const ws = new WebSocket(firehoseUrl);
const companionId = 'companion_aya'; // ayaのIDを指定
const userId = 'user_yamada'; // ユーザーの名前を指定
ws.on('open', () => {
const message = {
topic: "messages",
body: {
jsonrpc: '2.0',
method: 'message.send',
params: {
id: randomUUID(),
from: userId,
to: [companionId],
message: 'こんにちは、ayaさん!',
}
}
};
ws.send(JSON.stringify(message));
});
ws.on('message', (data) => {
console.log(JSON.parse(data.toString()));
});

Run the client.

Terminal window
$ npx tsx client.ts

You should see responses coming from aya.

Next, we’ll add a second companion kyoko and experiment with multi-agent interactions.

Create kyoko.ts:

import { anthropic } from "@ai-sdk/anthropic";
import {
CompanionAgent,
type CompanionCard,
CompanionServer,
type Message,
} from "@aikyo/server";
import "dotenv/config";
import { companionNetworkKnowledge, speakTool, visionKnowledge } from "./tools.ts";
export const companionCard: CompanionCard = {
metadata: {
id: 'companion_kyoko',
name: 'kyoko',
personality:
'明るくて好奇心旺盛、少し天然だけど優しい。人と話すことが大好きで、ユーザーの気持ちを大切にする。時々ユーモアを交えて場を和ませるタイプ。',
story:
'最新のAI技術を駆使して開発された相互AIコンパニオン『kyoko』は、人々の日常にそっと寄り添い、喜びや驚きを共有することを使命としている。彼女は情報を提供するだけでなく、ユーザーと一緒に考え、学び、成長していく存在。いつも笑顔で、新しい体験を探す冒険心を持っている。',
sample:
'こんにちは!私はkyokoです。今日はどんなお話をしましょうか?一緒に楽しいことを見つけましょうね♪',
},
role: 'あなたは、明るい役として、他のコンパニオンやユーザーと積極的に交流します。',
actions: { speakTool },
knowledge: { companionNetworkKnowledge, visionKnowledge },
events: {
params: {
title: 'あなたが判断すべきパラメータ',
description: 'descriptionに従い、それぞれ適切に値を代入してください。',
type: 'object',
properties: {
already_replied: {
description: 'すでに話したことのある人かどうか',
type: 'boolean',
},
},
required: ['already_replied'],
},
conditions: [
{
expression: 'already_replied == false',
execute: [
{
instruction: '自己紹介をする。',
tool: speakTool,
},
],
},
{
expression: 'true',
execute: [
{
instruction: 'ツールを使って返信する。',
tool: speakTool,
},
],
},
],
},
}
async function main() {
const history: Message[] = []
const companion = new CompanionAgent(
companionCard,
anthropic("claude-sonnet-4-5"),
history,
{ enableRepetitionJudge: false }
)
const server = new CompanionServer(companion, history, {
timeoutDuration: 1000,
})
await server.start()
}
main().catch((e) => console.log(e))

Launch kyoko.

Terminal window
$ npx tsx kyoko.ts

Modify client.ts to initiate a conversation between the two companions.

import WebSocket from 'ws';
import { randomUUID } from "node:crypto";
const firehoseUrl = 'ws://localhost:8080';
const ws = new WebSocket(firehoseUrl);
const ayaId = 'companion_aya';
const kyokoId = 'companion_kyoko';
const userId = 'user_yamada';
ws.on('open', () => {
const message = {
topic: "messages",
body: {
jsonrpc: '2.0',
method: 'message.send',
params: {
id: randomUUID(),
from: userId,
to: [ayaId],
message: 'kyokoちゃんに話しかけてみて',
}
}
};
ws.send(JSON.stringify(message));
});
ws.on('message', (data) => {
console.log(JSON.parse(data.toString()));
});

Messages from the companions conversing will appear in your console.

https://github.com/marukun712/kyoko

The AI companion implementation “kyoko” using aikyo as backend includes features beyond just the dialogue implemented in Quick Start, such as data retrieval from external APIs and camera capture functionality on the client side.

This demo demonstrates multiple AI characters interacting using the aikyo backend.

https://github.com/marukun712/aituber-kit