Add Olakai to existing AI code with minimal changes — wrap your LLM client, add custom metrics, and validate the integration. AUTO-INVOKE when user wants to: add monitoring to existing code, integrate observability into a working agent, add tracking to LLM calls, wrap OpenAI/Anthropic clients, add analytics to working AI code, or retrofit monitoring to production AI. TRIGGER KEYWORDS: olakai, add monitoring, integrate monitoring, wrap client, observability, SDK integration, @olakai/sdk, olakai-sdk, existing agent, existing code, add tracking, analytics, telemetry, retrofit monitoring, existing LLM, existing OpenAI, existing Anthropic. DO NOT load for: creating new agents from scratch (use olakai-new-project), troubleshooting issues (use olakai-troubleshoot), or generating reports (use olakai-reports).
How this skill is triggered — by the user, by Claude, or both
Slash command
/olakai-ai-olakai-skills:olakai-integrateThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
This skill guides you through adding Olakai monitoring to an existing AI agent or LLM-powered application with minimal code changes.
This skill guides you through adding Olakai monitoring to an existing AI agent or LLM-powered application with minimal code changes.
For full SDK documentation, see: https://app.olakai.ai/llms.txt
npm install -g olakai-cli && olakai login)olakai agents get AGENT_ID --json | jq '.apiKey')Note: Each agent can have its own API key. Create one with
olakai agents create --name "Name" --with-api-key
Adding monitoring is only the first step. The real value of Olakai comes from tracking custom KPIs specific to your agent's business purpose.
Without KPIs configured:
With KPIs configured:
⚠️ Plan to configure at least 2-4 KPIs that answer: "How do I know this agent is performing well?"
⚠️ KPIs are unique per agent. If adding monitoring to an agent that needs the same KPIs as another already-configured agent, you must still create new KPI definitions for this agent. KPIs cannot be shared or reused across agents.
Before adding monitoring, understand how custom data flows through Olakai:
SDK customData → CustomDataConfig (Schema) → Context Variable → KPI Formula → kpiData
| Rule | Consequence |
|---|---|
| Only CustomDataConfig fields become variables | Unregistered customData fields are NOT usable in KPIs |
| Formula evaluation is case-insensitive | stepCount, STEPCOUNT, StepCount all work in formulas |
| NUMBER configs need numeric values | Don't send "5" (string), send 5 (number) |
⚠️ IMPORTANT: The SDK accepts any JSON in
customData, but only fields registered as CustomDataConfigs are processed. Unregistered fields are stored but cannot be used in KPIs.
1. Install the SDK:
npm install @olakai/sdk
2. Wrap your existing client:
Before:
import OpenAI from "openai";
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
After:
import OpenAI from "openai";
import { OlakaiSDK } from "@olakai/sdk";
const olakai = new OlakaiSDK({ apiKey: process.env.OLAKAI_API_KEY! });
await olakai.init();
const openai = olakai.wrap(
new OpenAI({ apiKey: process.env.OPENAI_API_KEY }),
{ provider: "openai" }
);
That's it! All calls through openai are now automatically tracked. The SDK automatically captures the model name, token usage, and calculates execution cost using model-based pricing.
1. Install the SDK:
pip install olakai-sdk
2. Add instrumentation:
Before:
from openai import OpenAI
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
After:
from openai import OpenAI
from olakaisdk import olakai_config, instrument_openai
olakai_config(os.getenv("OLAKAI_API_KEY"))
instrument_openai()
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
That's it! All calls through client are now automatically tracked.
Pattern A: Single LLM Client You have one OpenAI/Anthropic client used throughout your app. → Use the wrapped client approach (shown above)
Pattern B: Multiple LLM Calls per Request Your agent makes several LLM calls to complete one task. → Use manual event tracking to aggregate calls
Pattern C: Streaming Responses You stream LLM responses to users. → SDK handles this automatically; events sent after stream completes
Pattern D: Third-Party LLM (not OpenAI/Anthropic)
You use Perplexity, Groq, local models, etc.
→ Use manual event tracking via REST API or olakai.event()
// lib/olakai.ts - Create a singleton
import { OlakaiSDK } from "@olakai/sdk";
import OpenAI from "openai";
let olakaiInstance: OlakaiSDK | null = null;
let wrappedOpenAI: OpenAI | null = null;
export async function getOlakaiClient(): Promise<OlakaiSDK> {
if (!olakaiInstance) {
olakaiInstance = new OlakaiSDK({
apiKey: process.env.OLAKAI_API_KEY!,
debug: process.env.NODE_ENV === "development",
retries: 3,
timeout: 30000,
});
await olakaiInstance.init();
}
return olakaiInstance;
}
export async function getOpenAI(): Promise<OpenAI> {
if (!wrappedOpenAI) {
const olakai = await getOlakaiClient();
wrappedOpenAI = olakai.wrap(
new OpenAI({ apiKey: process.env.OPENAI_API_KEY }),
{
provider: "openai",
defaultContext: {
task: "Software Development", // Default task category
},
}
);
}
return wrappedOpenAI;
}
# lib/olakai.py - Create initialization module
import os
from olakaisdk import olakai_config, instrument_openai
_initialized = False
def init_olakai():
global _initialized
if not _initialized:
olakai_config(
api_key=os.getenv("OLAKAI_API_KEY"),
debug=os.getenv("DEBUG") == "true"
)
instrument_openai()
_initialized = True
# Call at app startup
init_olakai()
TypeScript:
const response = await openai.chat.completions.create(
{
model: "gpt-4o",
messages: [{ role: "user", content: userMessage }],
},
{
userEmail: user.email, // Track by user
task: "Customer Experience", // Categorize
}
);
// Session grouping is automatic
Python:
from olakaisdk import olakai_context
with olakai_context(
userEmail=user.email,
userId=user.id, # Optional: explicit user tracking
task="Customer Experience"
):
response = client.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": user_message}]
)
# Note: Session grouping is automatic via internal sessionId
For assistive AI (chatbots/copilots), use sessionId to group multiple turns of a conversation together. This is required for CHAT-scoped KPIs (like classifier templates) that analyze the full conversation.
TypeScript:
// Group events by conversation for CHAT-scoped KPIs
olakai.event({
prompt: userMessage,
response: aiResponse,
sessionId: conversationId, // groups turns in the same conversation
userEmail: user.email,
});
Python:
with olakai_context(
userEmail=user.email,
sessionId=conversation_id, # groups turns in the same conversation
):
response = client.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": user_message}]
)
When to use
sessionId: If your agent handles multi-turn conversations and you want KPIs that evaluate the entire conversation (e.g., sentiment scoring, satisfaction), pass a consistentsessionIdacross all turns. For single-shot agentic workflows, session grouping is automatic and you typically do not need to set this explicitly.
⚠️ IMPORTANT: Only send fields you've registered as CustomDataConfigs (Step 5.3). Unregistered fields are stored but cannot be used in KPIs.
⚠️ Only send data you'll use in KPIs or for filtering. Don't duplicate fields already tracked by the platform:
- Session ID, Agent ID (automatic)
- User email (use
userEmailparameter)- Timestamps, token count, model name, execution cost, provider (automatic)
TypeScript:
const response = await openai.chat.completions.create(
{ model: "gpt-4o", messages },
{
userEmail: user.email,
customData: {
// Only include fields registered as CustomDataConfigs
Department: user.department,
ProjectId: currentProject.id,
Priority: ticket.priority,
// ❌ Don't add unregistered fields - they can't be used in KPIs
},
}
);
Python:
with olakai_context(
userEmail=user.email,
customData={
# Only include fields registered as CustomDataConfigs
"Department": user.department,
"ProjectId": project.id,
"Priority": ticket.priority
}
):
response = client.chat.completions.create(...)
If your agent makes multiple LLM calls per task, aggregate them into a single event.
taskExecutionId— Critical for multi-agent workflows. If multiple agents collaborate on the same task (e.g., a Planner, Researcher, and Writer), the orchestrator must generate ONEtaskExecutionIdand pass it to all agents. This is how Olakai correlates cross-agent work as a single logical task. Without it, analytics can only group by session (per-agent), losing the full picture of multidisciplinary tasks.
async function processDocument(doc: Document): Promise<ProcessingResult> {
const olakai = await getOlakaiClient();
const openai = await getOpenAI();
const startTime = Date.now();
let totalTokens = 0;
// Step 1: Extract
const extraction = await openai.chat.completions.create({
model: "gpt-4o",
messages: [{ role: "user", content: `Extract from: ${doc.content}` }],
});
totalTokens += extraction.usage?.total_tokens ?? 0;
// Step 2: Analyze
const analysis = await openai.chat.completions.create({
model: "gpt-4o",
messages: [{ role: "user", content: `Analyze: ${extraction.choices[0].message.content}` }],
});
totalTokens += analysis.usage?.total_tokens ?? 0;
// Step 3: Summarize
const summary = await openai.chat.completions.create({
model: "gpt-4o",
messages: [{ role: "user", content: `Summarize: ${analysis.choices[0].message.content}` }],
});
totalTokens += summary.usage?.total_tokens ?? 0;
const result = summary.choices[0].message.content ?? "";
// Track the complete workflow as ONE event
// ⚠️ Only send fields registered as CustomDataConfigs
olakai.event({
prompt: `Process document: ${doc.title}`,
response: result,
tokens: totalTokens,
requestTime: Date.now() - startTime,
taskExecutionId: crypto.randomUUID(), // Share across agents for cross-agent task analytics
task: "Data Processing & Analysis",
customData: {
// Only registered fields - see Step 5.3
DocumentId: doc.id,
DocumentType: doc.type,
StepCount: 3,
Success: 1, // Use 1/0 for boolean in NUMBER fields
},
});
return { summary: result, tokens: totalTokens };
}
⚠️ This step is required to get real value from Olakai. Without KPIs, you're only logging events - not gaining actionable insights.
| Without KPIs | With KPIs |
|---|---|
| Raw event logs only | Aggregated business metrics |
| No dashboard insights | Visual performance trends |
| No alerting | Threshold-based alerts |
| No ROI tracking | Calculated business value |
npm install -g olakai-cli
olakai login
# Create agent entry (associate with a workflow)
olakai agents create --name "Document Processor" --description "Processes and summarizes documents" --workflow WORKFLOW_ID --with-api-key
# Note the agent ID returned
⚠️ Every agent MUST belong to a workflow, even if it's the only agent.
# Check if agent has a workflow
olakai agents get YOUR_AGENT_ID --json | jq '.workflowId'
# If null, create a workflow and associate:
olakai workflows create --name "Your Workflow Name" --json
olakai agents update YOUR_AGENT_ID --workflow WORKFLOW_ID
Why workflows matter:
⚠️ IMPORTANT: Create configs for ALL fields you send in
customData. Only registered fields can be used in KPIs. CustomDataConfigs are now agent-scoped, so use--agent-id.
# Replace YOUR_AGENT_ID with the actual agent ID from step 5.1
# For each field in your customData, create a config
olakai custom-data create --agent-id YOUR_AGENT_ID --name "DocumentId" --type STRING
olakai custom-data create --agent-id YOUR_AGENT_ID --name "DocumentType" --type STRING
olakai custom-data create --agent-id YOUR_AGENT_ID --name "StepCount" --type NUMBER
olakai custom-data create --agent-id YOUR_AGENT_ID --name "Success" --type NUMBER # Use 1/0 for boolean
# Verify all configs exist for this agent
olakai custom-data list --agent-id YOUR_AGENT_ID
What this enables:
customData with these names are processedcustomData field NOT listed here is ignored for KPI purposes⚠️ Both CustomDataConfigs and KPIs are created for THIS agent specifically. They are bound to a single agent. If another agent needs the same fields/metrics, create them again with that agent's ID.
olakai kpis create \
--name "Documents Processed" \
--agent-id YOUR_AGENT_ID \
--calculator-id formula \
--formula "IF(Success = 1, 1, 0)" \
--aggregation SUM
olakai kpis create \
--name "Avg Steps per Document" \
--agent-id YOUR_AGENT_ID \
--calculator-id formula \
--formula "StepCount" \
--aggregation AVERAGE
After creating configs, ensure your SDK code sends exactly those field names:
customData: {
DocumentId: doc.id, // Matches CustomDataConfig "DocumentId"
DocumentType: doc.type, // Matches CustomDataConfig "DocumentType"
StepCount: 3, // Matches CustomDataConfig "StepCount"
Success: true ? 1 : 0, // Matches CustomDataConfig "Success"
// ❌ Don't add fields without configs - they won't be usable in KPIs
}
// app/api/chat/route.ts
import { NextRequest, NextResponse } from "next/server";
import { getOpenAI } from "@/lib/olakai";
import { auth } from "@/auth";
export async function POST(req: NextRequest) {
const session = await auth();
if (!session?.user) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const { message, conversationId } = await req.json();
const openai = await getOpenAI();
const response = await openai.chat.completions.create(
{
model: "gpt-4o",
messages: [{ role: "user", content: message }],
},
{
userEmail: session.user.email!,
task: "Customer Experience",
}
);
// Session grouping is automatic
return NextResponse.json({
reply: response.choices[0].message.content,
});
}
// middleware/olakai.ts
import { getOlakaiClient, getOpenAI } from "../lib/olakai";
export async function initOlakai() {
await getOlakaiClient();
console.log("Olakai initialized");
}
// routes/chat.ts
import express from "express";
import { getOpenAI } from "../lib/olakai";
const router = express.Router();
router.post("/", async (req, res) => {
const openai = await getOpenAI();
const { message } = req.body;
const response = await openai.chat.completions.create(
{ model: "gpt-4o", messages: [{ role: "user", content: message }] },
{ userEmail: req.user.email }
);
// Session grouping is automatic
res.json({ reply: response.choices[0].message.content });
});
# main.py
from fastapi import FastAPI, Depends
from openai import OpenAI
from olakaisdk import olakai_config, instrument_openai, olakai_context
app = FastAPI()
@app.on_event("startup")
async def startup():
olakai_config(os.getenv("OLAKAI_API_KEY"))
instrument_openai()
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
@app.post("/chat")
async def chat(message: str, user: User = Depends(get_current_user)):
with olakai_context(userEmail=user.email, task="Customer Support"):
response = client.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": message}]
)
return {"reply": response.choices[0].message.content}
The SDK automatically handles streaming. Events are sent after the stream completes:
const stream = await openai.chat.completions.create(
{
model: "gpt-4o",
messages: [{ role: "user", content: userMessage }],
stream: true,
},
{ userEmail: user.email }
);
for await (const chunk of stream) {
// Stream to client
res.write(chunk.choices[0]?.delta?.content ?? "");
}
// Event automatically sent here with full response
Wrap calls to ensure errors are tracked:
try {
const response = await openai.chat.completions.create({
model: "gpt-4o",
messages,
});
return response.choices[0].message.content;
} catch (error) {
// SDK still tracks the failed attempt
// Optionally send explicit error event
olakai.event({
prompt: messages[messages.length - 1].content,
response: `Error: ${error instanceof Error ? error.message : "Unknown"}`,
task: "Software Development",
customData: { error: true, errorType: error.name },
});
throw error;
}
For Anthropic, Perplexity, or other providers, use manual tracking:
import Anthropic from "@anthropic-ai/sdk";
const anthropic = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
async function callClaude(prompt: string): Promise<string> {
const startTime = Date.now();
const response = await anthropic.messages.create({
model: "claude-sonnet-4-20250514",
max_tokens: 1024,
messages: [{ role: "user", content: prompt }],
});
const content = response.content[0].type === "text" ? response.content[0].text : "";
// Manual tracking for non-wrapped clients
olakai.event({
prompt,
response: content,
tokens: response.usage.input_tokens + response.usage.output_tokens,
requestTime: Date.now() - startTime,
task: "Content Development",
customData: {
provider: "anthropic",
model: "claude-sonnet-4-20250514",
},
});
return content;
}
CRITICAL: Never assume your integration is working. Always validate by generating a test event and inspecting the actual data.
Run your application to trigger at least one LLM call:
# For a web app, make a test request
curl -X POST http://localhost:3000/api/chat -d '{"message": "test"}'
# For a script, run it
node my-agent.js "test input"
python my_agent.py "test input"
# Get the most recent event
olakai activity list --limit 1 --json
# Get full details (note the event ID from above)
olakai activity get EVENT_ID --json
Check the event was received:
olakai activity list --limit 1 --json | jq '.prompts[0] | {id, createdAt, app}'
If no event: Check API key, SDK initialization, and debug mode.
Check customData is present:
olakai activity get EVENT_ID --json | jq '.customData'
If missing or incomplete: Verify your SDK code passes customData correctly.
Check KPIs are numeric (if configured):
olakai activity get EVENT_ID --json | jq '.kpiData'
CORRECT:
{ "My KPI": 42 }
WRONG (formula stored as string):
{ "My KPI": "MyVariable" }
Fix with: olakai kpis update KPI_ID --formula "MyVariable"
WRONG (null value):
{ "My KPI": null }
Fix by ensuring:
olakai custom-data create --agent-id ID --name "MyVariable" --type NUMBER┌────────────────────────────────────────────────────┐
│ 1. Trigger LLM call (generate event) │
│ ↓ │
│ 2. Fetch: olakai activity get ID --json │
│ ↓ │
│ 3. Event exists? │
│ NO → Check API key, SDK init, debug mode │
│ ↓ │
│ 4. customData correct? │
│ NO → Fix SDK customData parameter │
│ ↓ │
│ 5. kpiData numeric? │
│ NO → olakai kpis update ID --formula "X" │
│ ↓ │
│ 6. kpiData not null? │
│ NO → Create CustomDataConfig, check case │
│ ↓ │
│ ✅ Integration validated │
└────────────────────────────────────────────────────┘
# 1. Trigger a test call
$ curl -X POST localhost:3000/api/chat -d '{"message":"hello"}'
{"reply":"Hi there!"}
# 2. Fetch the event
$ olakai activity list --limit 1 --json | jq '.prompts[0].id'
"cmkeabc123"
# 3. Inspect it
$ olakai activity get cmkeabc123 --json | jq '{customData, kpiData}'
{
"customData": {
"userId": "user-123",
"department": "Engineering"
},
"kpiData": {
"Response Quality": 8.5
}
}
# ✅ All values present and numeric - integration working!
After integrating monitoring, your agent is automatically set up for ROI tracking.
New agents automatically receive metric slot KPIs that provide standardized measurements with default formulas:
| Slot KPI | Output Unit | Description |
|---|---|---|
| Execution Cost | USD | Token-based cost estimate |
| Time Saved | minutes | time_saved_estimator classifier (CHAT scope) |
| Value Created | USD | Time Saved * hourly rate |
| Governance Compliance | % | Policy pass rate |
Plus a composite: ROI = Value Created / Execution Cost.
Verify the slot KPIs exist:
olakai kpis list --agent-id YOUR_AGENT_ID
# Look for slot KPIs: Execution Cost, Time Saved, Value Created, Governance Compliance
If the Time Saved classifier is missing (e.g., agent created via CLI), add it manually:
olakai kpis create --name "Time Saved" \
--calculator-id classifier --template-id time_saved_estimator \
--scope CHAT --agent-id YOUR_AGENT_ID
ROI = Value Created / Execution Cost
After generating events, verify classifier results appear in chat-level KPI data:
# Check that the classifier KPI is producing results
olakai activity sessions --agent-id YOUR_AGENT_ID
# Sessions with DECORATED status should have agentKpiData populated
# For detailed inspection
olakai activity list --agent-id YOUR_AGENT_ID --limit 5 --json
Important: Classifier KPIs run at the CHAT scope, meaning they evaluate the full conversation, not individual prompt requests. Ensure your events include a consistent
sessionIdto group conversation turns. Results appear after chat decoration runs (there may be a short delay).
1. Send 2-3 conversation turns with the same sessionId
2. Wait for chat decoration to process
3. Check: olakai activity sessions --agent-id YOUR_AGENT_ID
4. Verify sessions show DECORATED status with time saved values
| Application Type | Integration Point | Recommended Approach |
|---|---|---|
| API endpoint | Request handler | Wrap client, add user context |
| Background job | Job execution | Manual event at job completion |
| CLI tool | Command handler | Wrap client |
| Slack/Discord bot | Message handler | Wrap client with user context |
| Scheduled task | Cron function | Manual event with workflow aggregation |
| Category | Operators |
|---|---|
| Arithmetic | +, -, *, / |
| Comparison | <, <=, =, <>, >=, > |
| Logical | AND, OR, NOT |
| Conditional | IF(condition, true_val, false_val) |
| Null handling | ISNA(value), ISDEFINED(value) |
# Simple passthrough
--formula "StepCount"
# Percentage conversion
--formula "SuccessRate * 100"
# Conditional counting
--formula "IF(Success = 1, 1, 0)"
# Boolean detection to number
--formula "IF(PII detected, 1, 0)"
| Aggregation | Use For |
|---|---|
SUM | Totals, counts |
AVERAGE | Rates, percentages |
// Wrap client (automatic tracking)
const openai = olakai.wrap(new OpenAI({ apiKey }), { provider: "openai" });
// Add context to calls (session grouping is automatic)
await openai.chat.completions.create(params, {
userEmail: "[email protected]",
task: "Customer Experience",
customData: { key: "value" }
});
// Manual event (for aggregation or non-OpenAI)
olakai.event({
prompt: "input",
response: "output",
tokens: 1500,
requestTime: 5000,
sessionId: "conversation-id", // Groups turns in the same conversation (for CHAT-scoped KPIs)
taskExecutionId: "uuid-shared-across-agents", // Correlates events across agents in a multi-agent task
task: "Data Processing & Analysis",
customData: { workflowId: "abc" }
});
# Auto-instrumentation
olakai_config(api_key)
instrument_openai()
# Context for calls (sessionId groups conversation turns, taskExecutionId links events across agents)
with olakai_context(userEmail="[email protected]", sessionId="conversation-id", taskExecutionId="uuid-from-orchestrator", task="Support"):
response = client.chat.completions.create(...)
# Manual event
olakai_event(OlakaiEventParams(
prompt="input",
response="output",
tokens=1500,
customData={"key": "value"}
))
npx claudepluginhub olakai-ai/olakai-skillsGuides completion of development work by verifying tests, detecting environment, and presenting structured options for merge, PR, or cleanup.
Guides creation and editing of skills using test-driven development with pressure scenarios and subagents to verify agent compliance.
Dispatches multiple subagents concurrently for independent tasks without shared state. Use when facing 2+ unrelated failures or subsystems that can be investigated in parallel.