How to Build an AI Agent with Persistent Background Agents
AI agents are powerful, but most operate in a simple request-response loop: you give them a task, they work on it, and they come back when done. What if your agent could run multiple specialized sub-agents in the background simultaneously, each handling a different aspect of the task?
That is exactly what persistent background agents do. Meta's new Muse Code uses this architecture, and in this tutorial, we will show you how to build your own version using AI APIs.
What Are Persistent Background Agents?
Persistent background agents are specialized AI agents that:
- Stay active throughout a session (not spawned per-task)
- Work independently on subtasks
- Communicate results back to a main coordinator agent
- Avoid redundant work by remembering what they have already done
This architecture is particularly useful for complex tasks like codebase analysis, multi-file refactoring, or research tasks that benefit from parallel exploration.
Architecture Overview
Here is the system we will build:
User Task
|
v
[Main Coordinator Agent]
|
+---> [Background Agent 1: Code Explorer]
+---> [Background Agent 2: Test Runner]
+---> [Background Agent 3: Documentation Reader]
|
v
[Results Aggregated & Verified]
|
v
[Response to User]Prerequisites
- Node.js 18+ or Python 3.10+
- An API key from Qubax AI (gives you access to GPT, Claude, and more)
- Basic understanding of async programming
Step 1: Set Up Your Project
mkdir ai-background-agents
cd ai-background-agents
npm init -y
npm install openaiCreate a .env file:
QUBAX_API_KEY=your_api_key_here
QUBAX_BASE_URL=https://api.qubax.ai/v1Step 2: Create the Background Agent Class
const OpenAI = require('openai');
class BackgroundAgent {
constructor(name, systemPrompt, client) {
this.name = name;
this.systemPrompt = systemPrompt;
this.client = client;
this.memory = []; // Persistent memory across the session
this.active = false;
this.results = null;
}
async run(task) {
this.active = true;
const messages = [
{ role: 'system', content: this.systemPrompt },
{ role: 'user', content: task }
];
// Include relevant memory
if (this.memory.length > 0) {
const memoryContext = 'Previous findings:\n' +
this.memory.map(m => '- ' + m).join('\n');
messages.splice(1, 0, { role: 'assistant', content: memoryContext });
}
const response = await this.client.chat.completions.create({
model: 'gpt-5',
messages: messages,
temperature: 0.2,
max_tokens: 2000
});
const result = response.choices[0].message.content;
// Store in memory for future use
this.memory.push(result);
this.results = result;
this.active = false;
return result;
}
hasMemory() {
return this.memory.length > 0;
}
}Step 3: Create the Coordinator Agent
The coordinator agent receives the user's task, dispatches work to background agents, and synthesizes results.
class CoordinatorAgent {
constructor(apiKey, baseUrl) {
this.client = new OpenAI({
apiKey: apiKey,
baseURL: baseUrl
});
// Initialize specialized background agents
this.agents = {
explorer: new BackgroundAgent(
'Code Explorer',
'You are a code exploration agent. Analyze codebases and report structure, patterns, and potential issues. Be concise.',
this.client
),
tester: new BackgroundAgent(
'Test Runner',
'You are a testing agent. Design test cases and verify code correctness. Report pass/fail status and edge cases.',
this.client
),
reviewer: new BackgroundAgent(
'Code Reviewer',
'You are a code review agent. Check for best practices, security issues, and performance concerns. Suggest improvements.',
this.client
)
};
}
async executeTask(userTask) {
console.log('Coordinator: Received task:', userTask);
// Phase 1: Explore (parallel)
const exploreTask = `Analyze this task and identify what code areas are relevant: ${userTask}`;
const exploration = await this.agents.explorer.run(exploreTask);
console.log('Explorer:', exploration.substring(0, 100) + '...');
// Phase 2: Generate and Review (parallel)
const [testResults, reviewResults] = await Promise.all([
this.agents.tester.run(`Design tests for: ${userTask}. Context: ${exploration}`),
this.agents.reviewer.run(`Review approach for: ${userTask}. Context: ${exploration}`)
]);
// Phase 3: Synthesize
const synthesis = await this.client.chat.completions.create({
model: 'gpt-5',
messages: [
{
role: 'system',
content: 'You are a coordinator agent. Synthesize the results from your team into a final answer.'
},
{
role: 'user',
content: `Task: ${userTask}\n\nExploration: ${exploration}\n\nTests: ${testResults}\n\nReview: ${reviewResults}`
}
]
});
return synthesis.choices[0].message.content;
}
}Step 4: Add an Event Log (Crash Safety)
Inspired by Muse Code's crash-safe runtime, let's add an event log:
const fs = require('fs');
class EventLog {
constructor(logFile) {
this.logFile = logFile;
this.events = [];
this.load();
}
load() {
try {
const data = fs.readFileSync(this.logFile, 'utf8');
this.events = JSON.parse(data);
} catch (e) {
this.events = [];
}
}
append(event) {
const entry = {
timestamp: new Date().toISOString(),
...event
};
this.events.push(entry);
fs.writeFileSync(this.logFile, JSON.stringify(this.events, null, 2));
}
getLastState() {
// Find the last completed event
for (let i = this.events.length - 1; i >= 0; i--) {
if (this.events[i].type === 'task_complete') {
return this.events[i];
}
}
return null;
}
replay() {
console.log('Replaying event log:');
this.events.forEach(e => {
console.log(`[${e.timestamp}] ${e.type}: ${e.detail}`);
});
}
}Step 5: Integrate Everything
require('dotenv').config();
const coordinator = new CoordinatorAgent(
process.env.QUBAX_API_KEY,
process.env.QUBAX_BASE_URL
);
const eventLog = new EventLog('agent_events.json');
async function main() {
const task = process.argv[2] || 'Build a REST API endpoint for user registration';
eventLog.append({ type: 'task_start', detail: task });
try {
const result = await coordinator.executeTask(task);
eventLog.append({ type: 'task_complete', detail: 'Success', result });
console.log('\n=== FINAL RESULT ===\n');
console.log(result);
} catch (error) {
eventLog.append({ type: 'task_error', detail: error.message });
console.error('Task failed:', error.message);
// Check if we can resume
const lastState = eventLog.getLastState();
if (lastState) {
console.log('Last successful state:', lastState.detail);
}
}
}
main();Step 6: Run Your Agent
node index.js "Add input validation to the user registration endpoint"Output:
Coordinator: Received task: Add input validation to the user registration endpoint
Explorer: Analyzing task requirements...
Tester: Designing test cases for input validation...
Reviewer: Checking for security best practices...
=== FINAL RESULT ===
[Based on exploration, testing, and review, here is the validated solution...]Advanced: Using Different Models for Different Agents
One of the advantages of using Qubax AI is access to multiple models. You can use different models for different agents:
class MultiModelCoordinator {
constructor(apiKey, baseUrl) {
this.client = new OpenAI({ apiKey, baseURL: baseUrl });
// Use a fast, cheap model for exploration
this.agents.explorer = new BackgroundAgent(
'Explorer',
'You are a code explorer...',
this.client
);
this.agents.explorer.model = 'deepseek-v4-flash'; // Cheap and fast
// Use a powerful model for code generation
this.agents.coder = new BackgroundAgent(
'Coder',
'You are a code generation agent...',
this.client
);
this.agents.coder.model = 'gpt-5'; // Best quality
}
}This approach lets you optimize both cost and quality — use cheaper models for simple tasks and reserve expensive models for complex reasoning.
Best Practices
- Keep agents focused: Each background agent should have one clear responsibility
- Use memory wisely: Persistent memory is powerful but can grow large — prune periodically
- Handle failures gracefully: One agent failing should not crash the entire system
- Log everything: The event log is your safety net and debugging tool
- Start simple: Begin with 2-3 agents and add more as needed
FAQ
What is the difference between a background agent and a regular API call?
A regular API call is stateless — each call is independent. A background agent maintains state (memory) across multiple calls within a session, allowing it to build on previous work without re-exploring.
How many background agents should I use?
Start with 2-3 specialized agents. More is not always better — each agent adds complexity and API costs. Monitor performance and scale as needed.
Can I use this architecture with any AI model?
Yes. The architecture is model-agnostic. Using Qubax AI, you can swap between GPT-5, Claude, DeepSeek, and other models without changing your code.
How do I handle agent failures?
Implement retry logic with exponential backoff, and use the event log to track which agents succeeded and which failed. If an agent fails, the coordinator can retry or skip that step.
What is the cost of running multiple background agents?
Each agent makes its own API calls, so costs scale with the number of agents. Using cheaper models for simpler tasks (like exploration) and reserving expensive models for complex tasks keeps costs manageable. See Qubax AI pricing for current rates.
How does this compare to Meta's Muse Code architecture?
Muse Code uses a similar architecture at scale — persistent background agents with an event log for crash safety. This tutorial gives you a simplified version you can customize for your own use cases.
Ready to build your own AI agents? Get started with [Qubax AI](https://qubax.ai/docs) — one API, 20+ models, and the tools you need to build production-grade AI applications.