Architecture Overview — What You're Actually Working With
Before we get into code, here's the system architecture you need to understand:
Key Architectural Properties
Async-first design.
The API doesn't return AI-generated content synchronously. When you submit a prompt, you receive a presigned S3 URL immediately. The agent processes your request asynchronously and writes results to that URL. You poll for completion. Both the CLI and SDK abstract this pattern, but understanding it matters when you're designing integrations.
Agent-per-key isolation.
Each API key is bound to a single agent. One key = one agent = one set of RAG memories = one model configuration. If you need to interact with multiple agents, you use multiple keys. This is a deliberate architectural choice — it keeps the authentication surface simple and enables per-agent access control.
RAG is per-agent, not global.
Each agent has its own vector store. Documents uploaded to Agent A are not accessible to Agent B. This is critical for multi-tenant scenarios (agencies serving multiple clients) and for maintaining separation of concerns across agent roles.
Zero external dependencies (SDK).
The SDK is built entirely on Node.js built-in modules (https, fs, path). No axios, no node-fetch, no dependency chain to audit. This is a deliberate design choice for environments where dependency hygiene matters.
Authentication and API Keys
Key Format
API keys follow the pattern sk_live_ followed by a unique identifier:
sk_live_a1b2c3d4e5f6g7h8i9j0...
Where to Get Keys
- Log in to app.ceo.ai
- Navigate to API Keys
- Click Create API Key
- Select the agent this key connects to
- Copy the key — it's displayed once
Requirement: You must have an active paid subscription to create API keys.
Key-to-Agent Binding
Each API key maps to exactly one agent. When you make an API call with that key, the request is routed to that specific agent — including its system prompt, user prompt template, model configuration, and RAG memory.
If you're building a system that interacts with multiple agents, you'll manage multiple keys:
// Multi-agent setup
const supportAgent = new CeoAI({ apiKey: process.env.SUPPORT_AGENT_KEY });
const salesAgent = new CeoAI({ apiKey: process.env.SALES_AGENT_KEY });
const analysisAgent = new CeoAI({ apiKey: process.env.ANALYSIS_AGENT_KEY });
Authentication Methods
| Method | How | When |
|---|---|---|
| SDK constructor | new CeoAI({ apiKey: 'sk_live_...' }) |
Application code |
| CLI configuration | ceo configure --key sk_live_... |
Stored in ~/.ceo-ai/config.json (chmod 0600) |
| CLI inline override | ceo prompt "text" --key sk_live_... |
One-off commands, testing |
| Environment variable | CEO_API_KEY=sk_live_... ceo prompt "text" |
CI/CD pipelines, containers |
Security Notes
- Store keys in environment variables or secrets managers. Never commit them to source control.
- The CLI config file (
~/.ceo-ai/config.json) is created with0600permissions. config:showmasks the key in terminal output (sk_live_abc1••••••••def4).- If a key is compromised, revoke it immediately from the dashboard and create a new one. There is no expiration rotation mechanism — key lifecycle management is manual.
CLI — Installation, Configuration, and Core Commands
Installation
npm install -g @ceo-ai/cli
Requires Node.js 16+. Installs the ceo command globally.
Configuration
# Interactive setup
ceo configure
# Inline (skip prompts)
ceo configure --key sk_live_abc123
# Verify
ceo config:show
Full configuration options:
ceo configure \
--key sk_live_abc123 \
--endpoint https://ingestion.api.ceo.ai \
--name jules \
--conversation-dir ~/my-chats
| Flag | Description | Default |
|---|---|---|
--key | API key | — |
--endpoint | API endpoint URL | https://ingestion.api.ceo.ai |
--name | Custom command alias name | ceo |
--conversation-dir | Default directory for conversation files | ./ |
Core Commands Reference
ceo prompt <text>
Send a one-shot prompt. This is the fundamental command.
# Synchronous (wait for results)
ceo prompt "What was our Q4 revenue?" --poll
# Async (get presigned URL, poll later)
ceo prompt "Generate a detailed analysis"
# Save to file
ceo prompt "Generate report" --poll -o report.json
# JSON output (for piping)
ceo prompt "List customers" --poll --json | jq '.response'
# Disable RAG
ceo prompt "What is 2+2?" --no-rag --poll
# Custom polling settings
ceo prompt "Complex analysis" --poll --poll-interval 5000 --poll-timeout 300000
| Flag | Default | Description |
|---|---|---|
--poll | false | Wait for results instead of returning presigned URL |
--poll-interval | 2000 ms | Interval between poll attempts |
--poll-timeout | 120000 ms | Maximum wait time |
--no-rag | — | Disable RAG retrieval |
-o, --output | — | Write results to file (implies --poll) |
-k, --key | config | Override API key |
-e, --endpoint | config | Override endpoint |
--json | false | Raw JSON output |
ceo chat <text>
Persistent conversations with full context. Automatically polls and saves the exchange to a local JSON file.
# Start a conversation
ceo chat "What was our Q4 revenue?"
# Continue it (full history sent for context)
ceo chat "How does that compare to Q3?" -c conversation.json
# Named conversations
ceo chat "Analyze marketing spend" -c marketing.json
ceo chat "Break it down by channel" -c marketing.json
Conversation files are standard JSON containing metadata (agent info, model, credits, timestamps) and the complete message array. These files are portable — you can version control them, parse them, or feed them into other tools.
Conversation file structure:
{
"metadata": {
"agentId": "agent-abc-123",
"agentName": "Financial Analyst",
"model": "claude-sonnet-4-5",
"tenantId": "tenant-456",
"startedAt": "2025-02-14T18:30:00.000Z",
"lastUpdatedAt": "2025-02-14T18:35:00.000Z",
"totalCreditsUsed": 4500,
"exchangeCount": 3
},
"messages": [
{ "role": "user", "content": "...", "timestamp": "..." },
{ "role": "assistant", "content": "...", "timestamp": "...", "credits": 1500 }
]
}
ceo conversations
List all conversation files with metadata.
ceo conversations
ceo conversations --dir ~/my-chats
ceo poll <url>
Poll a presigned URL from a previous prompt submission.
ceo poll "https://ceo-ai-api-output-results.s3.amazonaws.com/..." -o result.json
Useful for two-step workflows: submit in one process/script, retrieve in another.
Custom Command Name
ceo configure --name jules
ceo alias:setup
# Follow the printed instructions to add the shell alias
# Then:
jules chat "What's the status?"
SDK — Installation, API Reference, and Integration Patterns
Installation
npm install @ceo-ai/sdk
Zero external dependencies. Node.js 16+.
Initialization
const { CeoAI } = require('@ceo-ai/sdk');
const ceo = new CeoAI({
apiKey: process.env.CEO_API_KEY, // Required
endpoint: 'https://ingest.api.ceo.ai', // Optional
timeout: 30000, // HTTP request timeout (ms)
pollInterval: 2000, // Default polling interval (ms)
pollTimeout: 120000 // Default polling timeout (ms)
});
Methods
ceo.prompt(text, options?) → Promise<PromptResponse>
Submit a prompt. Returns immediately with a presigned URL. Does NOT wait for results.
const result = await ceo.prompt('What was our Q4 revenue?');
// result:
// {
// presignedUrl: 'https://ceo-ai-api-output-results.s3.amazonaws.com/...',
// message: 'Processing request',
// estimatedCredits: 10,
// agentId: 'abc-123',
// agentName: 'Financial Analyst',
// model: 'claude-sonnet-4-5',
// tenantId: 'tenant-456'
// }
Options:
| Parameter | Type | Description |
|---|---|---|
options.ragMode | boolean | Enable/disable RAG. Default: true (server-side) |
options.conversationHistory | Array<{role, content}> | Previous messages for context |
ceo.promptAndWait(text, options?) → Promise<{ response, metadata }>
Submit + poll in one call. This is the convenience method for most use cases.
const { response, metadata } = await ceo.promptAndWait(
'Summarize our sales this quarter',
{
pollInterval: 3000,
pollTimeout: 60000,
onPoll: (attempt, elapsed) => {
console.log(`Waiting... attempt ${attempt} (${elapsed}ms)`);
}
}
);
Additional options:
| Parameter | Type | Description |
|---|---|---|
options.pollInterval | number | Override default polling interval (ms) |
options.pollTimeout | number | Override default polling timeout (ms) |
options.onPoll | Function | Callback: (attempt, elapsed) => {} |
ceo.pollForResult(url, options?) → Promise<Object | string>
Poll a presigned URL for results. Returns parsed JSON if possible, raw string otherwise.
// From a previously stored presigned URL
const result = await ceo.pollForResult(storedUrl, {
interval: 3000,
timeout: 60000,
onPoll: (attempt, elapsed) => console.log(`Attempt ${attempt}`)
});
ceo.addRag(content, filename, options?) → Promise<AddRagResponse>
Add file content as RAG memory. Covered in detail in the RAG Training section.
Programmatic RAG Training
This section is critical. RAG training is the difference between a generic agent and one that knows your (or your client's) business. Both the CLI and SDK provide mechanisms for programmatic knowledge ingestion. For a non-technical explanation, see the RAG Training Explained guide.
CLI: Single File
ceo addRag ./docs/pricing-guide.md
The file is automatically:
- Read and validated (max 4MB)
- Categorized by extension (code, documentation, data)
- Chunked (default: 2,000 character chunks)
- Embedded and stored in the agent's vector database
Options:
| Flag | Default | Description |
|---|---|---|
-c, --category | Auto-detected | Override: code, documentation, data, other |
--chunk-size | 2000 | Max chunk size in characters |
-k, --key | config | Override API key (target a specific agent) |
--json | false | Raw JSON output |
Auto-detected categories:
| Category | Extensions |
|---|---|
code | .js, .ts, .jsx, .tsx, .py, .java, .go, .rs, .rb, .php, .c, .cpp, .h, .cs, .swift, .kt, .sql, .sh, .bash, .yaml, .yml, .json, .xml, .html, .css, .scss |
documentation | .md, .mdx, .txt, .rst, .adoc, .pdf, .doc, .docx |
data | .csv, .tsv, .log |
CLI: Entire Directory
# Recursive ingestion of all supported files
ceo addRagDir ./docs --recursive
# Filter by extension
ceo addRagDir ./src --recursive --extensions js,ts,json
# Override category for all files
ceo addRagDir ./code-samples --category code
# Custom chunk size
ceo addRagDir ./docs --chunk-size 4000 --recursive
Output:
Found 5 files to add:
README.md
guide/getting-started.md
guide/advanced.md
api/endpoints.md
api/authentication.md
✓ README.md (3 chunks, 450 credits)
✓ guide/getting-started.md (5 chunks, 780 credits)
✓ guide/advanced.md (8 chunks, 1200 credits)
✓ api/endpoints.md (4 chunks, 620 credits)
✓ api/authentication.md (2 chunks, 310 credits)
--- Summary ---
Succeeded: 5
Credits: 3360
Default extensions included: md,txt,js,ts,py,json,yaml,yml,html,css,sql,sh,go,rs,rb,java,php,c,cpp,h
Automatically excluded: Hidden directories (.git, .env, etc.), node_modules, empty files, files >4MB.
SDK: Programmatic RAG
const fs = require('fs');
const { CeoAI } = require('@ceo-ai/sdk');
const ceo = new CeoAI({ apiKey: process.env.CEO_API_KEY });
// Single file
const content = fs.readFileSync('./docs/product-guide.md', 'utf8');
const result = await ceo.addRag(content, 'product-guide.md');
console.log(result);
// {
// success: true,
// filename: 'product-guide.md',
// category: 'documentation',
// totalChunks: 5,
// contentLength: 8420,
// memoriesAdded: 5,
// creditsDeducted: 2150,
// agentId: 'agent-123',
// tenantId: 'tenant-456'
// }
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
content | string | ✅ | File content as string |
filename | string | ✅ | Filename (used for category detection) |
options.category | string | — | Override: code, documentation, data, other |
options.chunkSize | number | — | Max chunk size (chars). Default: 2000 |
CI/CD Integration Pattern
High-value pattern. This is the pattern for teams that want to keep agent knowledge current automatically.
GitHub Actions example:
# .github/workflows/train-agent.yml
name: Update Agent Knowledge
on:
push:
branches: [main]
paths:
- 'docs/**'
- 'knowledge-base/**'
jobs:
train:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm install -g @ceo-ai/cli
- name: Train agent on updated docs
env:
CEO_API_KEY: ${{ secrets.CEO_SUPPORT_AGENT_KEY }}
run: |
ceo addRagDir ./docs --recursive --extensions md,txt
ceo addRagDir ./knowledge-base --recursive
What this does: Every time someone pushes changes to docs/ or knowledge-base/ on main, the agent's RAG memory is updated automatically. Your agent always has the latest documentation.
Shell script alternative (for cron or deployment scripts):
#!/bin/bash
# update-agent-knowledge.sh
echo "Updating support agent knowledge..."
CEO_API_KEY=$SUPPORT_AGENT_KEY ceo addRagDir ./docs --recursive --extensions md,txt
echo "Support agent updated."
echo "Updating sales agent knowledge..."
CEO_API_KEY=$SALES_AGENT_KEY ceo addRagDir ./sales-docs --recursive
echo "Sales agent updated."
echo "All agents updated."
SDK: Batch Directory Ingestion
The CLI's addRagDir handles directory traversal automatically. If you need the same behavior in application code (e.g., a custom training pipeline), here's the pattern:
const { CeoAI } = require('@ceo-ai/sdk');
const fs = require('fs');
const path = require('path');
const ceo = new CeoAI({ apiKey: process.env.CEO_API_KEY });
const SUPPORTED_EXTENSIONS = new Set([
'md', 'txt', 'js', 'ts', 'py', 'json', 'yaml', 'yml',
'html', 'css', 'sql', 'sh', 'go', 'rs', 'rb', 'java',
'php', 'c', 'cpp', 'h'
]);
const SKIP_DIRS = new Set(['node_modules', '.git', '.env', '__pycache__']);
const MAX_FILE_SIZE = 4 * 1024 * 1024; // 4MB
async function trainOnDirectory(dirPath, options = {}) {
const { recursive = true } = options;
const results = { succeeded: 0, failed: 0, totalCredits: 0 };
async function processDir(dir) {
const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
if (recursive && !SKIP_DIRS.has(entry.name) && !entry.name.startsWith('.')) {
await processDir(fullPath);
}
continue;
}
if (!entry.isFile()) continue;
const ext = path.extname(entry.name).slice(1).toLowerCase();
if (!SUPPORTED_EXTENSIONS.has(ext)) continue;
const stat = fs.statSync(fullPath);
if (stat.size === 0 || stat.size > MAX_FILE_SIZE) continue;
try {
const content = fs.readFileSync(fullPath, 'utf8');
const result = await ceo.addRag(content, entry.name);
console.log(`✓ ${entry.name} (${result.totalChunks} chunks, ${result.creditsDeducted} credits)`);
results.succeeded++;
results.totalCredits += result.creditsDeducted;
} catch (err) {
console.error(`✗ ${entry.name}: ${err.message}`);
results.failed++;
}
}
}
await processDir(dirPath);
return results;
}
// Usage
const results = await trainOnDirectory('./docs');
console.log(`Done: ${results.succeeded} succeeded, ${results.failed} failed, ${results.totalCredits} credits`);
Agent API — Request/Response Lifecycle
Understanding the async model is essential for building reliable integrations.
The Full Lifecycle
Step 1: Submit (ceo.prompt()) — Your code sends an HTTP POST with the prompt text, RAG mode flag, and optional conversation history. The API validates the key, identifies the agent, checks credit balance, and returns a presigned S3 URL where results will be written. This step is synchronous and fast (typically <1 second).
Step 2: Process (background) — The agent engine receives the job: retrieves relevant RAG chunks from the vector store, constructs the full prompt (system prompt + RAG context + user prompt + conversation history), sends it to the configured model, and writes the response to the presigned URL.
Step 3: Poll (ceo.pollForResult()) — Your code polls the presigned URL at a configured interval. When the content is available (HTTP 200 with a body), the result is returned. If not yet ready (HTTP 404 or empty body), polling continues until timeout.
promptAndWait() wraps Steps 1-3 into a single call. Use it unless you need to decouple submission from retrieval (e.g., fire-and-forget patterns, queue-based architectures, or very long-running tasks where you store the presigned URL and poll from a different process).
When to Use prompt() vs. promptAndWait()
| Scenario | Method | Why |
|---|---|---|
| Typical API integration (Express route, script) | promptAndWait | Simpler. Handles the full cycle. |
| Background job / queue worker | prompt + store URL | Decouple submission from retrieval. |
| Batch processing (many prompts at once) | prompt for all, then pollForResult | Submit concurrently, collect after. |
| Very long-running analysis (>2 min) | prompt + pollForResult with extended timeout | Avoid holding connections. |
| Webhook-driven architecture | prompt + store URL + poll on demand | Trigger collection when ready. |
Batch Processing Pattern
const questions = [
'What was Q1 revenue?',
'What was Q2 revenue?',
'What was Q3 revenue?',
'What was Q4 revenue?'
];
// Submit all concurrently
const submissions = await Promise.all(
questions.map(q => ceo.prompt(q))
);
console.log(`Submitted ${submissions.length} prompts`);
// Allow processing time
await new Promise(r => setTimeout(r, 15000));
// Collect all results
const results = await Promise.all(
submissions.map(s => ceo.pollForResult(s.presignedUrl, { timeout: 60000 }))
);
results.forEach((result, i) => {
console.log(`\n--- ${questions[i]} ---`);
console.log(result);
});
Conversation Management
CLI Conversations
The CLI handles conversation persistence automatically via local JSON files:
# Start
ceo chat "What was our Q4 revenue?"
# Creates conversation.json with the full exchange
# Continue (sends full history for context)
ceo chat "How does that compare to Q3?" -c conversation.json
# Named conversations for different topics
ceo chat "Analyze marketing spend" -c marketing.json
ceo chat "What about the Google Ads budget?" -c marketing.json
Every subsequent message in a conversation includes the full message history, giving the agent complete context.
SDK Conversations
The SDK doesn't manage conversation files — you manage the history array yourself:
const history = [];
// First exchange
const first = await ceo.promptAndWait('What was our Q4 revenue?');
history.push(
{ role: 'user', content: 'What was our Q4 revenue?' },
{ role: 'assistant', content: JSON.stringify(first.response) }
);
// Follow-up with context
const second = await ceo.promptAndWait('How does that compare to Q3?', {
conversationHistory: history
});
history.push(
{ role: 'user', content: 'How does that compare to Q3?' },
{ role: 'assistant', content: JSON.stringify(second.response) }
);
// Third turn — full context maintained
const third = await ceo.promptAndWait('What drove the change?', {
conversationHistory: history
});
Design decision for your integration: You control the history. This means you can:
- Store it in a database (for multi-session conversations)
- Truncate old messages if history gets too long
- Inject system context into the history
- Share history across different user interfaces (web → mobile → Slack)
Integrating into Existing Applications
Express.js
The most common integration pattern. Two endpoints: async (returns presigned URL) and sync (waits for result).
const express = require('express');
const { CeoAI, CeoAPIError } = require('@ceo-ai/sdk');
const app = express();
app.use(express.json());
const ceo = new CeoAI({ apiKey: process.env.CEO_API_KEY });
// Async — returns immediately with a presigned URL
app.post('/api/ask', async (req, res) => {
try {
const result = await ceo.prompt(req.body.question);
res.json({
presignedUrl: result.presignedUrl,
estimatedCredits: result.estimatedCredits
});
} catch (error) {
if (error instanceof CeoAPIError) {
res.status(error.statusCode).json({ error: error.message });
} else {
res.status(500).json({ error: 'Internal server error' });
}
}
});
// Sync — waits for the result (holds the connection open)
app.post('/api/ask/sync', async (req, res) => {
try {
const { response, metadata } = await ceo.promptAndWait(
req.body.question,
{ pollTimeout: 60000 }
);
res.json({ response, metadata });
} catch (error) {
if (error instanceof CeoAPIError) {
res.status(error.statusCode).json({
error: error.message,
details: error.details
});
} else {
res.status(500).json({ error: 'Internal server error' });
}
}
});
app.listen(3000);
Architectural note: The sync endpoint holds the HTTP connection open while polling. This is fine for internal tools and dashboards. For public-facing APIs or mobile clients, prefer the async pattern — submit via POST, return the presigned URL, and let the client poll (or implement a webhook callback).
Multi-Agent Application
// Initialize agents with different roles
const agents = {
support: new CeoAI({ apiKey: process.env.SUPPORT_KEY }),
sales: new CeoAI({ apiKey: process.env.SALES_KEY }),
analysis: new CeoAI({ apiKey: process.env.ANALYSIS_KEY }),
content: new CeoAI({ apiKey: process.env.CONTENT_KEY })
};
// Route requests to the appropriate agent
app.post('/api/support', async (req, res) => {
const { response } = await agents.support.promptAndWait(req.body.question);
res.json(response);
});
app.post('/api/proposal', async (req, res) => {
const { response } = await agents.sales.promptAndWait(
`Generate a proposal for: ${JSON.stringify(req.body.clientDetails)}`
);
res.json(response);
});
Each agent has its own knowledge (RAG), its own system prompt, and its own model. The routing logic in your application decides which agent handles which request.
Serverless (AWS Lambda)
const { CeoAI, CeoAPIError } = require('@ceo-ai/sdk');
const ceo = new CeoAI({ apiKey: process.env.CEO_API_KEY });
exports.handler = async (event) => {
const body = JSON.parse(event.body);
try {
const { response, metadata } = await ceo.promptAndWait(
body.question,
{ pollTimeout: 25000 } // Lambda timeout considerations
);
return {
statusCode: 200,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ response, metadata })
};
} catch (error) {
if (error instanceof CeoAPIError) {
return {
statusCode: error.statusCode,
body: JSON.stringify({ error: error.message })
};
}
return {
statusCode: 500,
body: JSON.stringify({ error: 'Internal server error' })
};
}
};
Lambda-specific note: Set pollTimeout below your Lambda timeout. If your Lambda has a 30-second timeout, set pollTimeout to 25 seconds. For longer-running tasks, use fire-and-forget: Lambda submits via ceo.prompt(), stores the presigned URL (in DynamoDB, SQS, etc.), and a second invocation collects results.
GitHub Integration — How the CEO Agent Commits Code
When you use the CEO Agent (via the web app or, on Enterprise, via the CEO Agent API), completed projects are committed to GitHub.
What Gets Committed
- Complete file structure — every generated file in its proper directory
- Individual commits per sub-task — each agent's work is a separate commit, so you can trace which agent generated which code
- Clean, readable code — not obfuscated or minified; production-quality, commented source files
- Full commit history — reviewable via
git log,git blame, etc.
How It Works
- You submit a project description to the CEO Agent
- The CEO Agent orchestrates the project (architect selection → spec → task assignment → execution)
- As each sub-agent completes its task, the generated code is committed to your connected GitHub repository
- The commit message identifies the sub-agent and the task
What This Means for Your Workflow
- You own the code. It's in your repo. No vendor lock-in on the output.
- You can review via standard tools. Pull requests, code review,
git diff— all work normally. - You can extend and modify. The generated code is a starting point. Your team can modify it like any other codebase.
- You get auditability. The commit history shows exactly what was generated, by which agent, for which task.
Practical Usage Pattern
- 1 Describe project to CEO Agent
- 2 CEO Agent generates and commits to a new branch
- 3 Your team reviews the branch (PR)
- 4 Modify/extend as needed
- 5 Merge when satisfied
This fits cleanly into existing Git workflows. The CEO Agent doesn't push to main — it creates commits you review and merge on your terms.
CEO Agent API (Enterprise) — Programmatic Project Orchestration
Availability: Enterprise plan ($5,500+/month)
The CEO Agent API lets you programmatically trigger the full CEO Agent workflow — project description in, orchestrated multi-agent output committed to GitHub.
When You'd Use This
- Automated app generation: A system that generates applications from templates or specifications
- Client onboarding automation: Automatically scaffold a client project based on intake form data
- Internal tool pipeline: Developers describe tools in Jira tickets, a pipeline generates them
- Agency scaling: Programmatically generate client deliverables
Key Difference from Agent API
| Agent API (all plans) | CEO Agent API (Enterprise) | |
|---|---|---|
| Scope | Single prompt → single response | Project description → full orchestrated project |
| Output | Text/data response | Multi-file codebase committed to GitHub |
| Agents involved | One (bound to your key) | Many (architect + team, auto-selected) |
| Use case | Embed intelligence in apps | Generate entire applications and systems |
If you're evaluating the Enterprise plan specifically for API-driven project generation, talk to the team about your architecture requirements.
Error Handling
SDK: CeoAPIError
const { CeoAI, CeoAPIError } = require('@ceo-ai/sdk');
try {
const { response } = await ceo.promptAndWait('Hello');
} catch (error) {
if (error instanceof CeoAPIError) {
console.log(error.message); // Human-readable error
console.log(error.statusCode); // HTTP status code
console.log(error.details); // Additional context (if any)
// Convenience getters
if (error.isAuthError) { /* 401 — invalid/expired key */ }
if (error.isInsufficientCredits) { /* 402 — not enough credits */ }
if (error.isRateLimited) { /* 429 — slow down */ }
if (error.isTimeout) { /* 408 — polling timed out */ }
}
}
Comprehensive Error Handling Pattern
async function queryAgent(question) {
try {
const { response, metadata } = await ceo.promptAndWait(question, {
pollTimeout: 60000
});
return { success: true, response, metadata };
} catch (error) {
if (!(error instanceof CeoAPIError)) {
// Network error, DNS failure, etc.
return { success: false, error: 'connection_error', message: error.message };
}
switch (true) {
case error.isAuthError:
// Key invalid or revoked. Don't retry.
return { success: false, error: 'auth', message: 'API key invalid' };
case error.isInsufficientCredits:
// Out of credits. Surface to user or trigger credit purchase.
return {
success: false,
error: 'credits',
required: error.details?.requiredCredits,
available: error.details?.availableCredits
};
case error.isRateLimited:
// Back off and retry.
await new Promise(r => setTimeout(r, 5000));
return queryAgent(question); // Simple retry — add max attempts in production
case error.isTimeout:
// Agent took too long. Increase timeout or use async pattern.
return { success: false, error: 'timeout', message: 'Agent processing exceeded timeout' };
default:
return { success: false, error: 'api_error', statusCode: error.statusCode, message: error.message };
}
}
}
CLI Error Messages
The CLI provides clear, actionable error messages:
# No key
$ ceo prompt "Hello"
Error: No API key configured. Run: ceo configure
# Bad key
$ ceo prompt "Hello" --key invalid
Error: Invalid API key format
# Insufficient credits
$ ceo prompt "Hello" --poll
✗ Request failed
Error: You need 50 credits but only have 20 available.
Details: {"requiredCredits":50,"availableCredits":20,"deficit":30}
# Timeout
$ ceo prompt "Hello" --poll --poll-timeout 5000
✗ Polling failed
Error: Polling timed out after 5000ms (3 attempts)
# File too large (RAG)
$ ceo addRag ./massive-file.pdf
Error: File too large (max 4MB)
Security and Data Isolation
Topics your security team will ask about:
Agent Isolation
Each agent is a separate entity with its own:
- API key
- System prompt and configuration
- RAG vector store (knowledge base)
- Usage tracking and credit accounting
There is no cross-agent data leakage. Agent A cannot access Agent B's RAG memories, even if they belong to the same tenant.
Community Agents and Data Privacy
Agents are private by default. If you opt an agent into the Community Agents marketplace:
- The agent's capabilities (model, type) become visible to the CEO Agent selection algorithm
- The agent's underlying RAG data remains private
- Other customers' tasks are processed through the agent's capabilities, not with access to its training data
You control which agents, if any, are shared. The default is private, and the majority of agents stay that way.
Data in Transit and at Rest
- All API communication is over HTTPS
- Presigned URLs use AWS S3 with time-limited access
- CLI config files are stored with
0600permissions
For Compliance Conversations
On Enterprise plans, the team can discuss specific compliance and security requirements for your environment. If your organization has particular data residency, encryption, or audit requirements, raise them during the setup call.
Performance Considerations and Credit Economics
Response Times
| Operation | Typical Latency |
|---|---|
prompt() — submit and get presigned URL | <1 second |
| Agent processing (simple prompt, small RAG) | 5-15 seconds |
| Agent processing (complex prompt, large RAG) | 15-60 seconds |
| CEO Agent project (small project) | 15-60 minutes |
| CEO Agent project (large project, many sub-tasks) | 1-3 hours |
addRag — single file | 2-5 seconds |
addRagDir — 50 files | 2-5 minutes |
Credit Consumption
Credits are the unit of consumption. They cover model inference, RAG retrieval, and processing.
| Plan | Monthly Credits | Rollover |
|---|---|---|
| Startup ($297) | 10,000 | ✅ |
| SMB ($1,499) | 50,000 | ✅ |
| Enterprise ($5,500+) | Custom | ✅ |
What consumes credits:
- Prompt processing (proportional to prompt length, RAG context, and model complexity)
- RAG ingestion (proportional to file size and chunk count)
- CEO Agent projects (proportional to task count and agent invocations)
Credit-efficient patterns:
- Use lighter models (e.g., Claude Haiku) for high-volume, simpler tasks
- Use heavier models (e.g., Claude Sonnet 4.5) only where quality demands it
- Minimize unnecessary RAG retrieval with
--no-rag/ragMode: falsefor prompts that don't need your knowledge base - Batch similar prompts together rather than submitting them one at a time with context-building
Monitoring Credit Usage
Track credit consumption in the CEO.ai dashboard. On SMB and Enterprise plans, you can enable auto-repurchase to prevent workflow interruption. Conversation files (CLI) include per-exchange credit tracking via the credits field in each assistant message.
Technical Evaluation Checklist
Your CEO asked you to evaluate CEO.ai. Here's the checklist to structure your assessment:
Want this checklist as a PDF?
Download it and share with your team or attach to your evaluation report.
Architecture
Integration
RAG / Knowledge Management
Operations
What's Not Available (Be Honest in Your Report)
No streaming responses.
The API uses a polling model. If your use case requires real-time token streaming (e.g., a ChatGPT-like typing effect), you'll need to implement this at the UI layer while polling.
No built-in webhook callbacks.
When a result is ready, you poll for it. The system doesn't push to your endpoint. For event-driven architectures, you'll need a polling worker.
Node.js only (SDK).
The SDK is JavaScript/Node.js. If your backend is Python, Go, Java, etc., you'll call the REST API directly or use the CLI from shell scripts.
Key rotation is manual.
No automated key rotation or expiration. Manage key lifecycle in your secrets manager.
Your Recommendation Framework
When reporting back to leadership:
If the primary use case is embedding AI into your product/app:
Evaluate the Agent API + SDK. Run the integration pattern that matches your architecture. Test with real prompts and real RAG data. Measure response quality and latency.
If the primary use case is building internal tools and apps:
Evaluate the CEO Agent. Submit a real project description for something your team needs. Review the generated code quality and completeness. Assess against what it would take to build manually.
If the primary use case is operational workflow automation:
Evaluate workflows + agents together. Map one existing manual process to a multi-agent workflow. Test the trigger → agent → agent → output pipeline.
In all cases, the guided setup call is the fastest way to evaluate. Bring your technical questions and a real use case — they'll build it with you.
Quick Reference Links
| Resource | URL |
|---|---|
| Dashboard | app.ceo.ai |
| CLI npm package | @ceo-ai/cli |
| SDK npm package | @ceo-ai/sdk |
| CLI docs | /developers/cli |
| SDK docs | /developers/sdk |
| Agent Builder | /platform/agent-builder |
| CEO Agent | /platform/ceo-agent |
| Workflows | /platform/workflows |
| Pricing | /pricing |
Ready to evaluate with real code?
Bring your architecture questions and a real use case to the setup call. We'll go as deep as you want on the technical details — and if something doesn't fit your stack, we'll tell you straight.