kavachOS

Cost attribution

Track LLM token spend, API call costs, and custom costs per agent, per tool, and per delegation chain.

Why cost attribution matters

When agents make LLM calls, every token has a price. In multi-agent systems where one agent can spawn or delegate to others, costs accumulate across multiple providers and chains. Without proper attribution you cannot answer basic questions: which agent is responsible for a $200 spike? Which tool is burning the most budget? Did the delegation chain for last night's job exceed its allocation?

Cost attribution gives you per-agent, per-tool, and per-delegation-chain cost records. It integrates with budget policies so you can fire alerts before spend becomes a problem, not after.

Setup

import { createKavach } from 'kavachos';
import { createCostAttributionModule } from 'kavachos/auth';

const kavach = await createKavach({
  database: { provider: 'sqlite', url: 'kavach.db' },
});

const costs = createCostAttributionModule(kavach.db, {
  currency: 'USD',
  retentionDays: 90,
  alertThresholds: { warn: 5.00, critical: 20.00 },
  onAlert: async (alert) => {
    console.warn(`[cost] ${alert.type}: agent ${alert.agentId} spent $${alert.currentCostUsd.toFixed(4)} (threshold: $${alert.threshold})`);
  },
});

Configuration options

Prop

Type

Recording costs

Call recordCost() after each LLM response or API call. Pass the raw token counts and the exact dollar amount from the provider's response.

// After an OpenAI completion
const completion = await openai.chat.completions.create({ model: 'gpt-4o', messages });

await costs.recordCost({
  agentId: agent.id,
  tool: 'openai:gpt-4o',
  inputTokens: completion.usage?.prompt_tokens,
  outputTokens: completion.usage?.completion_tokens,
  costUsd: calculateOpenAiCost(completion.usage),
});

RecordCostInput

Prop

Type

Costs are stored internally as integer microdollars (value × 1,000,000) to avoid floating-point drift across aggregations.

Provider helpers

function openAiCostUsd(usage: OpenAI.CompletionUsage, model: string): number {
  const rates: Record<string, { input: number; output: number }> = {
    'gpt-4o':        { input: 2.50 / 1_000_000, output: 10.00 / 1_000_000 },
    'gpt-4o-mini':   { input: 0.15 / 1_000_000, output: 0.60 / 1_000_000 },
  };
  const rate = rates[model] ?? { input: 0, output: 0 };
  return rate.input * usage.prompt_tokens + rate.output * usage.completion_tokens;
}

await costs.recordCost({
  agentId,
  tool: `openai:${completion.model}`,
  inputTokens: completion.usage.prompt_tokens,
  outputTokens: completion.usage.completion_tokens,
  costUsd: openAiCostUsd(completion.usage, completion.model),
});
function anthropicCostUsd(usage: Anthropic.Usage, model: string): number {
  const rates: Record<string, { input: number; output: number }> = {
    'claude-3-5-sonnet-20241022': { input: 3.00 / 1_000_000, output: 15.00 / 1_000_000 },
    'claude-3-5-haiku-20241022':  { input: 0.80 / 1_000_000, output: 4.00 / 1_000_000 },
  };
  const rate = rates[model] ?? { input: 0, output: 0 };
  return rate.input * usage.input_tokens + rate.output * usage.output_tokens;
}

await costs.recordCost({
  agentId,
  tool: `anthropic:${message.model}`,
  inputTokens: message.usage.input_tokens,
  outputTokens: message.usage.output_tokens,
  costUsd: anthropicCostUsd(message.usage, message.model),
});
// For any provider with a flat per-call cost
await costs.recordCost({
  agentId,
  tool: 'mcp:github',
  costUsd: 0.0001,
  metadata: { operation: 'create_issue', repo: 'acme/app' },
});

Generating cost reports

Per-agent report

const result = await costs.getAgentCost(agent.id);

if (result.success) {
  console.log('Total:', result.data.totalCostUsd.toFixed(4));
  console.log('By tool:', result.data.byTool);
  console.log('By day:', result.data.byDay);
}

Pass a custom period to narrow the query:

const result = await costs.getAgentCost(agent.id, {
  start: new Date('2025-01-01'),
  end: new Date('2025-01-31'),
});

CostReport

Prop

Type

Owner report

Aggregate cost across all agents owned by a user:

const result = await costs.getOwnerCost(userId);
if (result.success) {
  console.log(`Total spend for ${userId}: $${result.data.totalCostUsd.toFixed(2)}`);
}

Top agents by cost

Find the most expensive agents in any period:

const result = await costs.getTopAgentsByCost(10, {
  start: new Date('2025-01-01'),
  end: new Date('2025-01-31'),
});

if (result.success) {
  for (const { agentId, totalCostUsd } of result.data) {
    console.log(`${agentId}: $${totalCostUsd.toFixed(4)}`);
  }
}

Delegation chain report

When agents delegate to sub-agents, you can attribute all costs back to the originating chain by passing delegationChainId in recordCost():

// When the parent agent creates a delegation
const chain = await kavach.delegate({
  fromAgent: parentAgent.id,
  toAgent: childAgent.id,
  permissions: [{ resource: 'tool:summarize', actions: ['execute'] }],
  expiresIn: '1h',
});

// In the child agent's handler
await costs.recordCost({
  agentId: childAgent.id,
  tool: 'openai:gpt-4o-mini',
  costUsd: 0.02,
  delegationChainId: chain.id,
});

// Later: total cost across all agents in that chain
const result = await costs.getDelegationChainCost(chain.id);

Setting up alerts

Alerts fire automatically when recordCost() is called. There are three alert types:

TypeWhen it fires
warn24-hour rolling spend crosses the warn threshold
critical24-hour rolling spend crosses the critical threshold
budget_exceededMonthly spend crosses the maxTokensCostPerMonth limit from a budget policy
const costs = createCostAttributionModule(kavach.db, {
  alertThresholds: {
    warn: 5.00,      // $5 in 24 hours
    critical: 20.00, // $20 in 24 hours
  },
  onAlert: async (alert) => {
    if (alert.type === 'budget_exceeded') {
      // Revoke or suspend the agent
      await kavach.agent.revoke(alert.agentId);
    }

    // Send to Slack, PagerDuty, etc.
    await notifyOpsChannel({
      text: `[${alert.type.toUpperCase()}] Agent ${alert.agentId} spent $${alert.currentCostUsd.toFixed(2)} (limit: $${alert.threshold}) over ${alert.period}`,
    });
  },
});

CostAlert

Prop

Type

Integration with budget policies

checkBudget() reads budget policies created via kavach.policies and compares them against actual spend from the cost events table. This gives you a real-time answer before authorizing an operation.

const result = await costs.checkBudget(agent.id);

if (result.success) {
  const { withinBudget, spent, limit, remaining } = result.data;

  if (!withinBudget) {
    return new Response('Agent has exceeded its monthly cost budget', { status: 402 });
  }

  console.log(`$${spent.toFixed(4)} of $${limit?.toFixed(2) ?? '∞'} used`);
}

To set a budget limit, create a policy with maxTokensCostPerMonth:

await kavach.policies.create({
  agentId: agent.id,
  limits: {
    maxTokensCostPerMonth: 50, // $50/month
  },
  action: 'block',
});

The budget_exceeded alert fires automatically on recordCost() when spend crosses this limit, so you do not need to poll.

Maintenance

Cost events accumulate. Run cleanup() periodically to remove events older than the retention window:

// In a cron job
const result = await costs.cleanup({ retentionDays: 90 });
if (result.success) {
  console.log(`Deleted ${result.data.deleted} cost events`);
}

If you configured retentionDays on the module, you can call cleanup() with no arguments and it uses that value.

Return types

All methods return a Result<T> union:

type Result<T> =
  | { success: true; data: T }
  | { success: false; error: KavachError };

Check result.success before accessing result.data. Error codes:

CodeCause
RECORD_COST_FAILEDDatabase insert failed
GET_AGENT_COST_FAILEDQuery failed for agent report
GET_OWNER_COST_FAILEDQuery failed for owner report
GET_TOP_AGENTS_FAILEDAggregation query failed
GET_CHAIN_COST_FAILEDChain attribution query failed
CHECK_BUDGET_FAILEDBudget policy lookup failed
CLEANUP_FAILEDCleanup delete failed

On this page