Skip to main content

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

currency
string
default:"'USD'"
ISO 4217 currency code for cost records.
alertThresholds
{ warn: number; critical: number }
default:"undefined (no threshold alerts)"
Dollar amounts for 24-hour rolling spend that trigger alerts.
onAlert
default:"undefined"
Called when a threshold is crossed or a budget policy is exceeded.
retentionDays
number
default:"90"
How many days of cost events to keep. Older rows are deleted on cleanup().

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

agentId
string
The agent that incurred this cost.
tool
string
Provider and model identifier, e.g. ‘openai:gpt-4o’, ‘anthropic:claude-3-5-sonnet’, ‘mcp:github’.
inputTokens
number
default:"undefined"
Prompt tokens consumed.
outputTokens
number
default:"undefined"
Completion tokens generated.
costUsd
number
Exact cost in the configured currency.
metadata
default:"undefined"
Any additional data to store alongside this event (request IDs, model version, etc.).
delegationChainId
string
default:"undefined"
When set, this event is also attributed to the given delegation chain.
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),
});

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

agentId
string
Agent (or owner/chain) this report covers.
period
{ start: Date; end: Date }
The time window the report covers.
totalCostUsd
number
Total cost across all events in the period.
byTool
Cost breakdown per tool, sorted by cost descending.
byDay
Daily spend as YYYY-MM-DD strings, sorted ascending.

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

type
'warn' | 'critical' | 'budget_exceeded'
Severity of the alert.
agentId
string
The agent that triggered the alert.
currentCostUsd
number
The actual spend that triggered the alert.
threshold
number
The limit that was crossed.
period
string
Time window for this alert, e.g. ‘24h’ or ‘monthly’.

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