SDK & Integration
The Koladr SDK is how your agents communicate with the control plane. This guide covers installation, the core API methods, and a complete real-world example.
How the SDK Works
The SDK provides a thin client that sends structured data to the Koladr API. Your agent uses four primary methods:
startRun(): opens a new run sessionlogEvents(): records events during the runrequestAction(): requests permission to take an action and (when allowed) executes it through the connectorendRun(): closes the run with a final status
Every call is authenticated with your agent API key. The SDK attaches the key to each request, serializes the body as JSON, and surfaces typed errors via KoladrError.
Installation
npm install @koladr/sdkThe SDK is a TypeScript-first package with full type definitions. It works in Node.js, Bun, and any server-side JavaScript runtime.
Initialization
import { KoladrClient } from "@koladr/sdk";
const koladr = new KoladrClient(
process.env.KOLADR_BASE_URL!, // e.g. "https://app.koladr.com"
process.env.KOLADR_AGENT_KEY!,
);The constructor takes two positional arguments: the Koladr base URL (the host where your dashboard lives) and the agent API key you generated in Settings → API Keys. Treat the API key like a password — load it from environment variables or a secrets manager and never commit it to source control.
Starting a Run
Call startRun() at the beginning of every agent task. Provide a triggerType (what initiated the run); you can also include an externalRef to correlate the run with a record in your own system, an agentVersion for change tracking, and a triggeredBy identifier.
const { runId, status } = await koladr.startRun({
triggerType: "support-ticket",
externalRef: "TKT-1042",
agentVersion: "1.4.2",
});
console.log("Run started:", runId, "status:", status);The response is { runId, status }. Use runId in all subsequent logEvents(), requestAction(), and endRun() calls.
Logging Events
Use logEvents() to record what your agent is doing during a run. Events build the timeline that operators and approvers see in the dashboard.
// Koladr reserves sequenceNo: 1 for the run-started event,
// so your first agent event should start at 2.
await koladr.logEvents(runId, [
{
eventType: "context.retrieved",
sequenceNo: 2,
payload: {
source: "knowledge-base",
documentsFound: 3,
relevanceScore: 0.92,
},
},
{
eventType: "intent.classified",
sequenceNo: 3,
payload: {
intent: "refund-request",
confidence: 0.95,
reason: "Customer reported defective product",
},
},
]);Each event needs a unique, monotonic sequenceNo within the run. Koladr reserves 1 for the run-started event it inserts in startRun(), so your first agent event should start at 2. The occurredAt field is optional and defaults to the server time when the event lands.
✦Log generously
Requesting an Action
When your agent needs to perform a real-world action, use requestAction(). This is the critical control point where Koladr evaluates policies and (when the policy allows) dispatches the action through the matching connector.
const result = await koladr.requestAction({
runId,
actionType: "refund.create",
provider: "stripe",
resourceType: "charge",
resourceId: "ch_3OabcEXAMPLE",
payload: {
charge: "ch_3OabcEXAMPLE",
amount: 7999, // Stripe amounts are in cents (this is $79.99)
reason: "requested_by_customer",
},
evidence: {
confidence: 0.92,
customerTier: "premium",
orderAge: "3 days",
},
});Field reference:
runId: the run this action belongs to.actionType: the action string (e.g.refund.create,email.send,ticket.update). See Connectors for the full list each provider executes.provider: which connector should run the action — currentlystripe,gmail,hubspot, orzendesk.payload: the action-specific arguments. The policy engine exposes these aspayload.*in conditions.evidence(optional): risk signals like model confidence, retrieved sources, or customer tier. Exposed asevidence.*in conditions.resourceType/resourceId(optional): a stable handle for the external resource being acted on. Useful for the audit trail.
Handling Responses
Every requestAction() call resolves to a { decision, reason, ... } object. The shape of the rest of the object depends on the decision:
| Decision | Extra fields | What happened |
|---|---|---|
allow | execution | Policy allowed the action and the connector executed it. |
allow_with_alert | execution | Same as allow, plus an alert event was recorded. |
require_approval | approvalRequestId | Action is paused; an approver must decide before it runs. |
block | — | A policy blocked the action; reason explains why. |
switch (result.decision) {
case "allow":
case "allow_with_alert":
// Policy allowed the action; the connector executed it.
// result.execution is the action_executions row.
if (result.execution.status === "executed") {
console.log("Refund processed:", result.execution.response_payload);
} else {
console.error("Connector failed:", result.execution.response_payload);
}
break;
case "require_approval":
// A human must approve before the connector runs.
// Use result.approvalRequestId to poll or correlate.
console.log("Pending approval:", result.approvalRequestId);
break;
case "block":
// A policy blocked this action. result.reason explains why.
console.log("Blocked:", result.reason);
// Handle gracefully: inform the customer, escalate, etc.
break;
}When execution is present, it's the persisted action_executionsrow, including the connector'sstatus ("executed" or "failed") and the connector'sresponse_payload.
Ending a Run
Always end runs, even when errors occur. This keeps your run history clean and lets operators distinguish between completed and abandoned runs.
await koladr.endRun({
runId,
status: "completed", // or "failed" | "blocked"
});The status field accepts "completed", "failed", or "blocked". If a request was routed to require_approval, leave the run open — Koladr transitions it to awaiting_approval and resumes when the approver decides.
Full Example: Support Agent Refund Flow
Here is a complete example showing a support agent handling a refund request through Koladr:
import { KoladrClient, KoladrError } from "@koladr/sdk";
const koladr = new KoladrClient(
process.env.KOLADR_BASE_URL!,
process.env.KOLADR_AGENT_KEY!,
);
async function handleSupportTicket(ticket: {
id: string;
customerTier: string;
message: string;
}) {
// 1. Start a run.
const { runId } = await koladr.startRun({
triggerType: "support-ticket",
externalRef: ticket.id,
});
let nextSeq = 2; // sequenceNo: 1 is reserved for run_started.
try {
// 2. Log context retrieval.
await koladr.logEvents(runId, [
{
eventType: "context.retrieved",
sequenceNo: nextSeq++,
payload: { source: "knowledge-base", query: ticket.message },
},
]);
// 3. Agent determines a refund is appropriate.
await koladr.logEvents(runId, [
{
eventType: "intent.classified",
sequenceNo: nextSeq++,
payload: { intent: "refund-request", confidence: 0.95 },
},
]);
// 4. Request the refund through Koladr.
const result = await koladr.requestAction({
runId,
actionType: "refund.create",
provider: "stripe",
payload: {
charge: "ch_3OabcEXAMPLE",
amount: 7999, // $79.99 in cents
reason: "requested_by_customer",
},
evidence: {
confidence: 0.95,
customerTier: ticket.customerTier,
},
});
// 5. Record the outcome and end the run.
if (result.decision === "allow" || result.decision === "allow_with_alert") {
await koladr.logEvents(runId, [
{
eventType: "action.completed",
sequenceNo: nextSeq++,
payload: { execution: result.execution },
},
]);
await koladr.endRun({ runId, status: "completed" });
} else if (result.decision === "require_approval") {
await koladr.logEvents(runId, [
{
eventType: "action.pending",
sequenceNo: nextSeq++,
payload: { approvalRequestId: result.approvalRequestId },
},
]);
// Leave the run in "awaiting_approval"; Koladr will resume it
// when the approver decides.
} else {
await koladr.logEvents(runId, [
{
eventType: "action.blocked",
sequenceNo: nextSeq++,
payload: { reason: result.reason },
},
]);
await koladr.endRun({ runId, status: "blocked" });
}
} catch (error) {
if (error instanceof KoladrError && error.isUnauthorized) {
// Agent key is missing, unknown, or revoked. The API returns
// the same 401 for all three (constant-time, no enumeration);
// regenerate the key in the dashboard if the issue persists.
}
await koladr.endRun({ runId, status: "failed" });
throw error;
}
}ℹREST API
/api/ingest/run-start, /api/ingest/events, /api/actions/request, /api/ingest/run-end) and each request body includes the agent API key as agentKey.Error Handling
The SDK throws a single typed error class, KoladrError, for every non-2xx response. The error carries the HTTP status, an optional server-suppliedcode, and one convenience getter:
error.isUnauthorized: HTTP 401. The agent key is missing, unknown, or has been revoked.
ℹOne 401 for every auth failure
error.isRevoked getter is kept for backward compatibility but now aliases error.isUnauthorized.import { KoladrClient, KoladrError } from "@koladr/sdk";
try {
await koladr.requestAction({ /* ... */ });
} catch (error) {
if (error instanceof KoladrError) {
if (error.isUnauthorized) {
// 401 — agent key is missing, unknown, or revoked.
// Generate a new key in the Koladr dashboard and rotate.
} else if (error.status === 402) {
// Workspace billing is locked (past_due grace exceeded,
// expired trial, canceled). Surface the recovery summary.
} else if (error.status === 429) {
// Rate-limited — back off and retry.
} else {
// Generic Koladr error: error.status, error.code, error.message.
}
} else {
throw error;
}
}Wrap your Koladr calls in try/catch and always end the run in a finally block to prevent orphaned runs.
Next
Policies
Define rules for how agent actions are governed
