Skip to main content
The Agents API lets you interact with NimbleBrain agents programmatically. List available agents, create conversations, send messages, and stream responses in real-time.

Overview

Working with agents involves three main concepts:
  1. Agents - The AI assistants you’ve created in NimbleBrain Studio
  2. Conversations - Chat sessions with an agent (preserves context)
  3. Messages - Individual messages within a conversation
Agent
  └── Conversation
        ├── Message (user)
        ├── Message (assistant)
        ├── Message (user)
        └── Message (assistant)

Agents

List Agents

Retrieve all agents in your workspace:
const agents = await nb.agents.list();

for (const agent of agents) {
  console.log(`${agent.name} (${agent.id})`);
  console.log(`  Type: ${agent.type}`);
  console.log(`  Description: ${agent.description}`);
}
Response type:
interface Agent {
  id: string;
  name: string;
  type?: 'custom' | 'nira' | 'system';
  description?: string;
  createdAt?: string;
  updatedAt?: string;
}
The nira type indicates the built-in NIRA assistant. custom agents are ones you’ve created.

Conversations

Conversations maintain context across multiple messages. Always create a conversation before sending messages to an agent.

Create a Conversation

const conversation = await nb.conversations.create(
  agentId,
  'Optional title for this chat'
);

console.log(`Conversation ID: ${conversation.id}`);
Parameters:
  • agentId (string, required) - The ID of the agent
  • title (string, optional) - A descriptive title for the conversation

Get Conversation Details

const conversation = await nb.conversations.get(agentId, conversationId);

console.log(`Title: ${conversation.title}`);
console.log(`Messages: ${conversation.messageCount}`);
console.log(`Status: ${conversation.status}`);
Response type:
interface Conversation {
  id: string;
  title?: string;
  context?: string;
  status?: string;
  messageCount?: number;
  lastMessageAt?: string;
  createdAt?: string;
}

Messages

Send a Message (Non-Streaming)

For simple use cases where you don’t need real-time streaming:
const response = await nb.messages.send(
  agentId,
  conversationId,
  'What is the weather like today?'
);

console.log('Agent response:', response.content);
Response type:
interface SendMessageResponse {
  messageId?: string;
  content?: string;
  role?: string;
  executionId?: string;
  tokensUsed?: number;
}
Non-streaming requests block until the agent finishes responding. For long responses, this can take 30+ seconds. Use streaming for a better user experience.

Send a Message (Streaming)

Stream the response in real-time for a typewriter effect:
for await (const event of nb.messages.stream(agentId, conversationId, 'Tell me a story')) {
  switch (event.type) {
    case 'message.start':
      console.log('Agent started responding...');
      break;
    case 'content':
      // Display text as it arrives
      process.stdout.write(event.data.text as string);
      break;
    case 'tool.start':
      console.log(`\n[Using tool: ${event.data.display}]`);
      break;
    case 'tool.complete':
      console.log('[Tool finished]');
      break;
    case 'done':
      console.log('\n--- Response complete ---');
      break;
    case 'error':
      console.error('Error:', event.data.error);
      break;
  }
}
See Streaming for more details on building streaming chat interfaces.

List Messages in a Conversation

Retrieve the full message history:
const messages = await nb.messages.list(agentId, conversationId);

for (const message of messages) {
  console.log(`[${message.role}]: ${message.content}`);
}
Response type:
interface Message {
  id: string;
  role: 'user' | 'assistant' | 'system';
  content: string;
  createdAt?: string;
}

Complete Example: Chat Application

Here’s a complete example of a simple chat loop:
import { NimbleBrain } from '@nimblebrain/sdk';
import * as readline from 'readline';

async function chat() {
  const nb = new NimbleBrain({ apiKey: process.env.NIMBLEBRAIN_API_KEY! });

  // Get the first agent
  const agents = await nb.agents.list();
  if (agents.length === 0) {
    console.log('No agents found');
    return;
  }

  const agent = agents[0];
  console.log(`Chatting with: ${agent.name}\n`);

  // Create a conversation
  const conversation = await nb.conversations.create(agent.id, 'CLI Chat');

  // Set up readline for user input
  const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
  });

  const askQuestion = () => {
    rl.question('You: ', async (input) => {
      if (input.toLowerCase() === 'quit') {
        rl.close();
        return;
      }

      // Stream the response
      process.stdout.write('Agent: ');
      for await (const event of nb.messages.stream(agent.id, conversation.id, input)) {
        if (event.type === 'content') {
          process.stdout.write(event.data.text as string);
        }
      }
      console.log('\n');

      askQuestion(); // Continue the loop
    });
  };

  console.log('Type "quit" to exit\n');
  askQuestion();
}

chat();

Error Handling

try {
  const conversation = await nb.conversations.create(agentId);
} catch (error) {
  if (error instanceof Error) {
    if (error.message.includes('404')) {
      console.error('Agent not found');
    } else if (error.message.includes('401')) {
      console.error('Invalid API key');
    } else {
      console.error('Error:', error.message);
    }
  }
}

Best Practices

Reuse Conversations

Keep the same conversation ID to maintain context across multiple messages

Use Streaming

Always use streaming for user-facing applications for better UX

Handle Errors

Wrap all API calls in try-catch and handle specific error types

Store Conversation IDs

Persist conversation IDs if users need to resume chats later