briyah
v1.1.6
Published
Briyah multi-agent AI SDK
Readme
Briyah SDK
Briyah is an SDK for AI agents, multi-agent rooms, and interactive stories. It wraps the Briyah runtime as a single class with per-user data isolation and balance tracking, and supports Anthropic, OpenAI, Google, Grok, DeepSeek, Together, and Fal as LLM providers.
What's in the package:
- Agents. AI agents with a system prompt, persistent conversation history, attached documents, and per-call cost and token tracking. Histories can be summarized/compacted or cleared.
- Rooms. Multi-agent conversations with a shared goal. Agents exchange messages at different visibility levels (speak, whisper, think, shout), draft and approve shared artifacts, and a moderator can route turns.
- Stories. Text-based interactive fiction with a narrator, AI-played characters, and a human player. Stories progress through chapters and can introduce new characters mid-game. Story events (state updates, character suggestions, chapter transitions, errors) are delivered through an emitter the host app subscribes to.
- Text processing. One-shot
processTextfor non-conversational use, with cost and token counts on the result. - File attachments. Attach documents to agents as context. Supported file types vary by LLM provider.
- Per-user data. Agents, rooms, stories, attached files, balances, and transactions are scoped to a userId the host controls. Per-user directories keep state isolated.
- Balances and transactions. Track per-user balances against usage cost. Record pending, succeeded, failed, or cancelled transactions when integrating with a payment provider, credit balances on webhook confirmation, and resume stories that paused on insufficient funds. Briyah does not process payments itself.
- Real-time event emitters. Story-state and balance updates can be forwarded to SSE or websockets.
State is persisted as JSON files under a configurable data path; no external database is required.
Installation
npm install briyahimport { Briyah } from 'briyah';Quick Start
import { Briyah } from 'briyah';
// 1. Create and initialize Briyah instance
const briyah = new Briyah({
dataPath: './my-briyah-data',
userServiceCacheTimeoutMinutes: 30,
startingBalance: 10.00
});
await briyah.init();
// 2. Get user-specific service (userId managed by your app)
const appService = briyah.getAppService('user-123');
// 3. Use full Briyah functionality
// createAgent() registers the agent in memory immediately — agent.id is usable right away.
// Call agent.save() when you want to persist it to disk.
const agent = appService.createAgent(
'Anthropic',
'AI Assistant',
'James',
'A helpful AI assistant',
'claude-haiku-4-5'
);
if (!agent.id) throw new Error('Agent creation failed');
const result = await appService.processText(agent.id, 'Hello! How are you?');
console.log(result.result);
agent.save(); // persist to disk
// 4. Cleanup when done
await briyah.shutdown();Configuration
BriyahConfigOptions
interface BriyahConfigOptions {
/**
* Base directory for Briyah data storage
* Default: './briyah-data' relative to process.cwd()
*/
dataPath?: string;
/**
* Timeout in minutes for cached user services
* Default: 30 minutes
*/
userServiceCacheTimeoutMinutes?: number;
/**
* Starting balance for new users (in dollars)
* Default: value from process.env.STARTING_BALANCE
*/
startingBalance?: number;
/**
* Override environment variables
* Useful for testing or multi-instance configurations
*/
envOverrides?: Record<string, string>;
}Environment Variables
LLM Provider API Keys (at least one required):
ANTHROPIC_API_KEY- For Claude modelsOPENAI_API_KEY- For GPT modelsGOOGLE_GENAI_API_KEY- For Google AI (Gemini) modelsXAI_API_KEY- For Grok modelsDEEPSEEK_API_KEY- For DeepSeek modelsTOGETHER_API_KEY- For Together AI modelsFAL_API_KEY- For Fal image-generation models
Optional:
STARTING_BALANCE- Starting balance for new users (e.g., "10.00")BRIYAH_DATA_PATH- Global default data path (overridden by constructordataPath)USER_SERVICE_CACHE_TIMEOUT_MINUTES- Cache timeout (overridden by constructor option)
Logging
Briyah writes logs to {dataPath}/logs/briyah.log by default. Configure or disable via the logging option:
const briyah = new Briyah({
dataPath: './my-data',
logging: {
enabled: true, // default
logFile: './my-data/logs/app.log', // default: {dataPath}/logs/briyah.log
console: false, // set true to also log to stdout/stderr
level: 'warn', // 'debug' | 'log' | 'warn' | 'error' — default: 'log'
}
});
// Log to console only (no file):
const briyah = new Briyah({ logging: { console: true, logFile: null } });
// Disable logging entirely:
const briyah = new Briyah({ logging: { enabled: false } });API Reference
Briyah Class
constructor(options?: BriyahConfigOptions)
Creates a new Briyah SDK instance.
const briyah = new Briyah({
dataPath: './briyah-data',
startingBalance: 10.00
});async init(): Promise<void>
Initializes the Briyah system. Must be called before using getAppService().
await briyah.init();getAppService(userId: string): AppService
Gets the AppService instance for a specific user. The AppService provides full access to:
- Agent management
- Room conversations
- Story generation
- Text processing
- File attachments
const appService = briyah.getAppService('user-123');Throws: Error if init() has not been called.
removeUserService(userId: string): void
Manually removes a user's service from the cache. Useful for:
- Forcing a fresh AppService instance
- Cleaning up when a user logs out
briyah.removeUserService('user-123');getCacheStats()
Returns statistics about cached user services.
const stats = briyah.getCacheStats();
console.log(`Cached users: ${stats.cachedUsers}`);async shutdown(): Promise<void>
Shuts down the Briyah system and cleans up resources.
await briyah.shutdown();isInitialized(): boolean
Checks if Briyah has been initialized.
if (briyah.isInitialized()) {
const appService = briyah.getAppService('user-123');
}AppService
The AppService provides full access to Briyah functionality.
Common operations:
// Agent Management
const agent = appService.createAgent(provider, name, nickname, description, model);
const agents = await appService.listAgents();
await appService.deleteAgent(agentId);
await appService.reloadAgent(agentId); // Clear conversation history (keeps any prior summary)
await appService.compactAgentConversation(agentId); // Summarize history into a single message
// Room Management
const room = await appService.createRoom(name, goal, agentIds);
const rooms = await appService.listRooms();
await appService.sendRoomMessage(roomId, content, sender, action?);
// Story Management
const story = await appService.createStory(name, idea, userCharacterDesc, otherCharactersDesc, storyModel?);
await appService.progressStory(storyId);
// Text Processing
const result = await appService.processText(agentId, text);
// File Attachments
const attachment = await appService.attachDocument(agentId, fileName, fileData);
await appService.deleteAttachedFile(agentId, fileName); // by filename
await appService.deleteAttachedFileById(documentId); // by document IDUsage Examples
Example 1: Multi-User Setup
import { Briyah } from 'briyah';
// Initialize once at application startup
const briyah = new Briyah({
dataPath: './data/briyah',
startingBalance: 10.00
});
await briyah.init();
// In your route handler or service
async function handleUserRequest(userId: string, message: string) {
const appService = briyah.getAppService(userId);
// Each user gets their own isolated AppService
const agents = await appService.listAgents();
// ... process user request
}
// Cleanup at application shutdown
process.on('SIGINT', async () => {
await briyah.shutdown();
process.exit(0);
});Example 2: Custom Data Path
import { Briyah } from 'briyah';
import path from 'path';
const briyah = new Briyah({
dataPath: path.join(__dirname, '..', 'user-data', 'briyah'),
userServiceCacheTimeoutMinutes: 60 // 1 hour cache
});
await briyah.init();Example 3: Testing Configuration
import { Briyah } from 'briyah';
const testBriyah = new Briyah({
dataPath: './test-data',
startingBalance: 100.00,
userServiceCacheTimeoutMinutes: 5,
envOverrides: {
ANTHROPIC_API_KEY: process.env.TEST_ANTHROPIC_KEY,
OPENAI_API_KEY: process.env.TEST_OPENAI_KEY
}
});
await testBriyah.init();
// Run tests...
await testBriyah.shutdown();Example 4: Agent Creation and Text Processing
const appService = briyah.getAppService('user-123');
// Create an agent
const agent = appService.createAgent(
'Anthropic', // provider
'AI Assistant', // name
'James', // nickname
'A helpful AI assistant', // description
'claude-haiku-4-5' // model
);
if (!agent.id) throw new Error('Agent creation failed');
console.log(`Created agent: ${agent.id}`);
// Process text with the agent
const result = await appService.processText(
agent.id,
'What is the capital of France?'
);
console.log(`Response: ${result.result}`);
console.log(`Cost: $${result.totalCost}`);
console.log(`Tokens: ${(result.totalInputTokens ?? 0) + (result.totalOutputTokens ?? 0)}`);Example 5: Multi-Agent Room Conversation
const appService = briyah.getAppService('user-123');
// Create agents
const analyst = appService.createAgent('OpenAI', 'Analyst', 'Analyst', 'Data analyst', 'gpt-4');
const writer = appService.createAgent('Anthropic', 'Writer', 'Writer', 'Content writer', 'claude-haiku-4-5');
if (!analyst.id || !writer.id) throw new Error('Agent creation failed');
// Create a room with both agents
const room = await appService.createRoom(
'Analysis Session',
'Analyze sales data and produce a written summary',
[analyst.id, writer.id]
);
// Send a message to start the conversation
await appService.sendRoomMessage(
room.roomId,
'Please analyze the sales data for Q4',
'Analyst',
'speak'
);
// The agents will automatically respond and interact
// Check room messages to see the conversation
const messages = await appService.getRoomMessages(room.roomId);
console.log(`Messages: ${messages.messages.length}`);Example 6: Story Message Emitter
The story message emitter delivers real-time events during gameplay. Subscribe to it after creating the story but before the first player turn, so no events are missed.
import type {
StoryStateEvent,
StoryIntroduceCharacterEvent,
StoryProgressChapterEvent,
StoryErrorEvent,
} from 'briyah';
const app = briyah.getAppService('user-123');
const story = await app.createStory(
'The Lost Kingdom',
'A medieval fantasy where ancient ruins hold a terrible secret',
'A disgraced knight seeking redemption'
);
const emitter = app.getStoryMessageEmitter(story.id);
// Fires after every turn with the full updated game state.
emitter.on('story-state', async (event: StoryStateEvent) => {
const { state } = event;
console.log('Latest message:', state.latestRoomMessage?.content);
// Detect the player's turn by comparing speaker names.
// Do NOT use state.humanPrompt for this — it is a UI display string,
// not a reliable turn indicator.
if (state.currentSpeaker === state.userAgentName) {
const input = await promptPlayer(state.humanPrompt);
await app.respondToStory(story.id, input);
}
});
// Fires when the narrator wants to introduce a new character.
// The host app should present this to the player before acting.
// Accept by calling introduceCharacterToStory, or decline with declineCharacter.
emitter.on('suggest-introduce-character', async (event: StoryIntroduceCharacterEvent) => {
const accept = await askPlayer(`Add ${event.characterName} to the story?`);
if (accept) {
await app.introduceCharacterToStory(story.id, event.characterName!, '', undefined, true);
} else {
await app.declineCharacter(story.id, event.characterName!);
}
});
// Fires when the narrator wants to advance to the next chapter.
// Call progressStory to accept. Ignoring the event stays in the current chapter —
// there is no explicit rejection call.
emitter.on('suggest-progress-chapter', async (_event: StoryProgressChapterEvent) => {
const accept = await askPlayer('The narrator suggests ending this chapter. Continue?');
if (accept) {
await app.progressStory(story.id);
}
});
// Fires on fatal errors during room processing.
// Named 'story-error' rather than 'error' to avoid Node.js throwing when no
// listener is attached.
emitter.on('story-error', (event: StoryErrorEvent) => {
if (event.errorType === 'InsufficientBalanceError') {
console.error('Out of credits:', event.message);
} else {
console.error(`Story error [${event.errorType}]:`, event.message);
}
});Example 7: Recording Payments and Managing Balance
Briyah does not process payments itself — your application handles the payment provider interaction. Once a payment is confirmed, use AppService to record the transaction, credit the user's balance, and restart any stories that stalled due to insufficient funds.
const app = briyah.getAppService(userId);
// --- When your payment provider initiates a charge ---
// Record it as pending so the transaction log stays consistent.
await app.recordTransaction(amount, paymentIntentId, 'pending');
// --- When your payment provider webhook confirms success ---
await app.updateTransactionStatus(paymentIntentId, 'succeeded');
await app.addBalance(amount);
// Resume any stories that paused mid-turn due to InsufficientBalanceError.
await app.resumePausedStories();
// --- When a payment fails or is cancelled ---
await app.updateTransactionStatus(paymentIntentId, 'failed');
// --- Querying balance and history ---
const balance = app.getBalance();
const { transactions, total } = await app.getTransactions(50, 0);
const tx = await app.getTransactionByPaymentId(paymentIntentId);
console.log(tx?.status); // 'pending' | 'succeeded' | 'failed' | 'cancelled'To push real-time balance updates to a connected client (e.g. via SSE), subscribe to the balance emitter before crediting the user:
const emitter = app.getBalanceMessageEmitter(userId);
emitter.on('update', (newBalance: number) => {
// Push newBalance to the client
});Data Storage
Briyah stores data in the following structure:
{dataPath}/
├── common/ # Shared across all users
│ ├── config/ # LLM pricing and image-model config
│ ├── prompts/ # Global prompt templates
│ └── published-agents.json # Published agent mappings
└── user/
└── {userId}/ # Per-user data
├── prompts/ # User-specific prompts
├── upload/ # User file attachments
└── userconfig/ # User configuration files
├── agents.json # User's agents
├── rooms.json # User's rooms
├── stories.json # User's stories
└── balance.json # User's balanceMulti-Instance Support
You can create multiple Briyah instances with different configurations:
const briyah1 = new Briyah({ dataPath: './data1' });
const briyah2 = new Briyah({ dataPath: './data2' });
await briyah1.init();
await briyah2.init();
// Each instance has isolated data
const user1Service = briyah1.getAppService('user-123');
const user2Service = briyah2.getAppService('user-123');
// These are completely separate services with different data pathsNote: Singleton services (LLM providers, message emitters) are shared across all Briyah instances within the same process. This is safe because they are stateless or keyed by ID.
Troubleshooting
"Must call init() before getAppService()"
Make sure to call await briyah.init() before calling getAppService().
"No LLM provider API keys configured"
Set at least one of the required API key environment variables (ANTHROPIC_API_KEY, OPENAI_API_KEY, etc.).
"STARTING_BALANCE environment variable is required"
Set the STARTING_BALANCE environment variable or pass startingBalance in the constructor options.
Cache not working as expected
Check the cache statistics with getCacheStats() and adjust userServiceCacheTimeoutMinutes in the constructor options.
