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:
Agents - The AI assistants you’ve created in NimbleBrain Studio
Conversations - Chat sessions with an agent (preserves context)
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