mcpbeat Sign in

Convex Agents Usage Tracking Skill for Claude

Tracks LLM token consumption and usage metrics for billing, monitoring, and optimization. Use this to log token usage, calculate costs, generate invoices, and understand which agents or users consume the most resources.

4k tokens
context cost
the whole folder, loaded on every use
2
files
instructions only
0
copies elsewhere
how many repositories repackaged it
320
stars on the repo
on the repository, not the skill itself

Install

one command, takes just this skill from the repository
npx skills add https://github.com/Microck/ordinary-claude-skills --skill Convex Agents Usage Tracking

What comes with it

675 bytes besides the instruction
metadata.json

The instruction itself

16 sections, as written by the author

Purpose

Usage tracking records how many tokens each agent uses, enabling accurate billing, cost monitoring, and performance optimization. Essential for understanding LLM costs and user impact.

When to Use This Skill

  • Billing users based on token consumption
  • Monitoring API costs
  • Optimizing agent efficiency
  • Tracking usage by user, agent, or team
  • Generating invoices and cost reports
  • Alerting on high usage
  • Analyzing cost trends

How to Use It

1. Configure Usage Handler

Create a handler to log usage:

// convex/agents/myAgent.ts
import { Agent } from "@convex-dev/agent";
import { components } from "../_generated/api";
import { openai } from "@ai-sdk/openai";
import { internal } from "../_generated/api";

const myAgent = new Agent(components.agent, {
  name: "My Agent",
  languageModel: openai.chat("gpt-4o-mini"),
  usageHandler: async (ctx, args) => {
    const {
      userId,
      threadId,
      agentName,
      model,
      provider,
      usage, // { inputTokens, outputTokens, totalTokens }
      providerMetadata,
    } = args;

    // Save usage to database
    await ctx.runMutation(internal.usage.recordUsage, {
      userId,
      threadId,
      agentName,
      model,
      provider,
      inputTokens: usage.inputTokens,
      outputTokens: usage.outputTokens,
      totalTokens: usage.totalTokens,
      timestamp: Date.now(),
    });
  },
});

2. Store Usage Records

Save usage data for later analysis:

// convex/usage.ts
import { internalMutation } from "./_generated/server";
import { v } from "convex/values";
import { defineTable, defineSchema } from "convex/server";

export const schema = defineSchema({
  usage: defineTable({
    userId: v.string(),
    threadId: v.optional(v.string()),
    agentName: v.optional(v.string()),
    model: v.string(),
    provider: v.string(),
    inputTokens: v.number(),
    outputTokens: v.number(),
    totalTokens: v.number(),
    cost: v.number(), // Cost in dollars
    date: v.string(), // ISO date for daily rollups
    billingPeriod: v.string(), // YYYY-MM for monthly
  })
    .index("billingPeriod_userId", ["billingPeriod", "userId"])
    .index("date_userId", ["date", "userId"])
    .index("userId", ["userId"]),

  invoices: defineTable({
    userId: v.string(),
    billingPeriod: v.string(),
    amount: v.number(),
    status: v.union(
      v.literal("pending"),
      v.literal("paid"),
      v.literal("failed")
    ),
    generatedAt: v.number(),
  })
    .index("billingPeriod_userId", ["billingPeriod", "userId"])
    .index("userId", ["userId"]),
});

export const recordUsage = internalMutation({
  args: {
    userId: v.string(),
    threadId: v.optional(v.string()),
    agentName: v.optional(v.string()),
    model: v.string(),
    provider: v.string(),
    inputTokens: v.number(),
    outputTokens: v.number(),
    totalTokens: v.number(),
  },
  handler: async (
    ctx,
    {
      userId,
      threadId,
      agentName,
      model,
      provider,
      inputTokens,
      outputTokens,
      totalTokens,
    }
  ) => {
    const today = new Date().toISOString().split("T")[0];
    const billingPeriod = today.substring(0, 7); // YYYY-MM

    // Calculate cost (example: $0.015 per 1M input tokens, $0.060 per 1M output)
    const cost =
      (inputTokens / 1_000_000) * 0.015 + (outputTokens / 1_000_000) * 0.06;

    await ctx.db.insert("usage", {
      userId,
      threadId,
      agentName,
      model,
      provider,
      inputTokens,
      outputTokens,
      totalTokens,
      cost,
      date: today,
      billingPeriod,
    });
  },
});

3. Query Usage by User

Get total usage for a specific user:

// convex/usage.ts
import { query } from "./_generated/server";
import { v } from "convex/values";

export const getUserUsage = query({
  args: { userId: v.string() },
  handler: async (ctx, { userId }) => {
    const records = await ctx.db
      .query("usage")
      .withIndex("userId", (q) => q.eq("userId", userId))
      .collect();

    const totals = records.reduce(
      (acc, record) => ({
        inputTokens: acc.inputTokens + record.inputTokens,
        outputTokens: acc.outputTokens + record.outputTokens,
        totalTokens: acc.totalTokens + record.totalTokens,
        cost: acc.cost + record.cost,
      }),
      { inputTokens: 0, outputTokens: 0, totalTokens: 0, cost: 0 }
    );

    return totals;
  },
});

export const getMonthlyUsageByUser = query({
  args: { billingPeriod: v.string() },
  handler: async (ctx, { billingPeriod }) => {
    const records = await ctx.db
      .query("usage")
      .withIndex("billingPeriod_userId", (q) =>
        q.eq("billingPeriod", billingPeriod)
      )
      .collect();

    // Group by userId
    const byUser: Record<string, any> = {};
    for (const record of records) {
      if (!byUser[record.userId]) {
        byUser[record.userId] = {
          userId: record.userId,
          totalTokens: 0,
          cost: 0,
          records: 0,
        };
      }
      byUser[record.userId].totalTokens += record.totalTokens;
      byUser[record.userId].cost += record.cost;
      byUser[record.userId].records += 1;
    }

    return Object.values(byUser);
  },
});

4. Generate Monthly Invoices

Create invoices from usage data:

// convex/usage.ts
import { action } from "./_generated/server";
import { v } from "convex/values";

export const generateInvoices = action({
  args: { billingPeriod: v.string() },
  handler: async (ctx, { billingPeriod }) => {
    // Get all usage for the period
    const records = await ctx.db
      .query("usage")
      .withIndex("billingPeriod_userId", (q) =>
        q.eq("billingPeriod", billingPeriod)
      )
      .collect();

    // Group by user
    const byUser: Record<string, number> = {};
    for (const record of records) {
      byUser[record.userId] = (byUser[record.userId] || 0) + record.cost;
    }

    // Create invoices
    for (const [userId, amount] of Object.entries(byUser)) {
      const existingInvoice = await ctx.db
        .query("invoices")
        .filter(
          (inv) =>
            inv.billingPeriod === billingPeriod && inv.userId === userId
        )
        .first();

      if (!existingInvoice) {
        await ctx.db.insert("invoices", {
          userId,
          billingPeriod,
          amount,
          status: "pending",
          generatedAt: Date.now(),
        });
      }
    }

    return { invoicesCreated: Object.keys(byUser).length };
  },
});

5. Track Usage by Agent

Compare efficiency across agents:

// convex/usage.ts
import { query } from "./_generated/server";
import { v } from "convex/values";

export const getUsageByAgent = query({
  args: { userId: v.string(), billingPeriod: v.string() },
  handler: async (ctx, { userId, billingPeriod }) => {
    const records = await ctx.db
      .query("usage")
      .withIndex("billingPeriod_userId", (q) =>
        q.eq("billingPeriod", billingPeriod)
      )
      .filter((r) => r.userId === userId)
      .collect();

    // Group by agent
    const byAgent: Record<string, any> = {};
    for (const record of records) {
      const agent = record.agentName || "unknown";
      if (!byAgent[agent]) {
        byAgent[agent] = {
          agent,
          totalTokens: 0,
          inputTokens: 0,
          outputTokens: 0,
          cost: 0,
          calls: 0,
        };
      }
      byAgent[agent].totalTokens += record.totalTokens;
      byAgent[agent].inputTokens += record.inputTokens;
      byAgent[agent].outputTokens += record.outputTokens;
      byAgent[agent].cost += record.cost;
      byAgent[agent].calls += 1;
    }

    return Object.values(byAgent).sort((a, b) => b.cost - a.cost);
  },
});

6. Set Up Scheduled Invoice Generation

Generate invoices automatically monthly:

// convex/crons.ts
import { cronJobs } from "convex/server";
import { internal } from "./_generated/api";

const crons = cronJobs();

// Generate invoices on the 2nd day of each month at midnight UTC
crons.monthly(
  "generateMonthlyInvoices",
  { day: 2, hourUTC: 0, minuteUTC: 0 },
  internal.usage.generateInvoices,
  { billingPeriod: calculatePreviousMonth() }
);

export default crons;

function calculatePreviousMonth(): string {
  const now = new Date();
  const month = now.getMonth() === 0 ? 11 : now.getMonth() - 1;
  const year = now.getMonth() === 0 ? now.getFullYear() - 1 : now.getFullYear();
  return `${year}-${String(month + 1).padStart(2, "0")}`;
}

7. Monitor Usage Alerts

Alert on high usage:

// convex/usage.ts
import { action } from "./_generated/server";
import { v } from "convex/values";

export const checkUsageAlerts = action({
  args: {},
  handler: async (ctx, {}) => {
    const today = new Date().toISOString().split("T")[0];

    // Get today's usage by user
    const records = await ctx.db
      .query("usage")
      .filter((r) => r.date === today)
      .collect();

    const byUser: Record<string, number> = {};
    for (const record of records) {
      byUser[record.userId] = (byUser[record.userId] || 0) + record.cost;
    }

    // Alert on users exceeding $100/day
    const alerts = [];
    for (const [userId, cost] of Object.entries(byUser)) {
      if (cost > 100) {
        alerts.push({ userId, cost, reason: "Daily spend exceeded $100" });
      }
    }

    // Send alerts (email, Slack, etc.)
    for (const alert of alerts) {
      await ctx.runMutation(internal.notifications.sendAlert, alert);
    }

    return alerts;
  },
});

8. Usage Analytics Dashboard

Query usage for display:

// convex/usage.ts
import { query } from "./_generated/server";

export const getDashboardStats = query({
  args: { userId: v.string() },
  handler: async (ctx, { userId }) => {
    // This month's usage
    const today = new Date();
    const billingPeriod = `${today.getFullYear()}-${String(
      today.getMonth() + 1
    ).padStart(2, "0")}`;

    const monthlyRecords = await ctx.db
      .query("usage")
      .withIndex("billingPeriod_userId", (q) =>
        q.eq("billingPeriod", billingPeriod)
      )
      .filter((r) => r.userId === userId)
      .collect();

    const monthlyStats = monthlyRecords.reduce(
      (acc, r) => ({
        totalTokens: acc.totalTokens + r.totalTokens,
        cost: acc.cost + r.cost,
      }),
      { totalTokens: 0, cost: 0 }
    );

    // All time
    const allRecords = await ctx.db
      .query("usage")
      .withIndex("userId", (q) => q.eq("userId", userId))
      .collect();

    const allTimeStats = allRecords.reduce(
      (acc, r) => ({
        totalTokens: acc.totalTokens + r.totalTokens,
        cost: acc.cost + r.cost,
      }),
      { totalTokens: 0, cost: 0 }
    );

    return {
      monthly: monthlyStats,
      allTime: allTimeStats,
      averageDailyCost: monthlyStats.cost / Math.min(today.getDate(), 30),
    };
  },
});

Key Principles

  • Record at generation time: Capture usage in usageHandler
  • Calculate costs accurately: Use provider-specific pricing
  • Monthly periods: Align invoices with calendar months
  • User attribution: Always track which user generated usage
  • Agent tracking: Know which agents are most expensive
  • Archival: Archive old usage data for compliance

Example: Complete Billing System

// convex/billing/complete.ts
import { Agent } from "@convex-dev/agent";
import { components } from "../_generated/api";
import { openai } from "@ai-sdk/openai";
import { internal } from "../_generated/api";

// Cost per million tokens
const PRICING = {
  "gpt-4o-mini": {
    input: 0.015,
    output: 0.06,
  },
};

export const billingAgent = new Agent(components.agent, {
  name: "Billing Agent",
  languageModel: openai.chat("gpt-4o-mini"),
  usageHandler: async (ctx, { usage, userId, model }) => {
    if (!userId) return;

    const pricing = PRICING[model as keyof typeof PRICING] || {
      input: 0.001,
      output: 0.002,
    };

    const cost =
      (usage.inputTokens / 1_000_000) * pricing.input +
      (usage.outputTokens / 1_000_000) * pricing.output;

    await ctx.runMutation(internal.billing.recordUsage, {
      userId,
      model,
      inputTokens: usage.inputTokens,
      outputTokens: usage.outputTokens,
      totalTokens: usage.totalTokens,
      cost,
    });
  },
});

Common Patterns

  • Per-user billing: Each user gets an invoice
  • Team billing: Aggregate across team members
  • Pay-as-you-go: Immediate billing per message
  • Monthly invoicing: Collect charges at month end
  • Usage tiers: Discounts for high-volume users
  • Prepaid credits: Users buy credit packages

Next Steps

  • Implement rate limiting: See Convex Agents Rate Limiting to control costs
  • Add alerts: Send notifications for high usage
  • Build dashboards: Display usage analytics to users
  • Optimize costs: Analyze which agents are most expensive

Troubleshooting

  • Missing usage records: Check usageHandler is configured
  • Pricing mismatches: Verify cost calculations match your provider
  • Large invoices: Check for runaway token generation
  • Monthly timing: Verify billing period calculation aligns with your fiscal year

Other skills for the same job

different authors, same section of the catalogue
Datapack Builder
by anthropics
vendor ×1

Build professional financial services data packs from various sources including CIMs, offering memorandums, SEC filings, web search, or MCP servers. Extract, normalize, and standardize financial data into investment committee-ready Excel workbooks with consistent structure, proper formatting, and documented assumptions. Use for M&A due diligence, private equity analysis, investment committee materials, and standardizing financial reporting across portfolio companies. Do not use for simple financial calculations or working with already-completed data packs.

6k tokens
Agent Wallet
by internet-court
×1

Give the AI agent its own EVM wallet with admin-controlled policies the agent CANNOT bypass even under prompt injection. Encrypted keystore (AES-256-GCM, scrypt KDF), policy file the agent has no tool to write, deterministic policy gate on every signing operation, optional local HTTP dashboard. Triggers: agent wallet, give the agent a wallet, agent address, fund the agent, agent autonomy, policy gate, kill switch, agent permissions, bounded autonomy, ERC-4337 alternative, session-key alternative.

4k tokens
Bnbchain MCP
by internet-court
×1

BNB Chain MCP server connection and tool usage. Covers npx @bnb-chain/mcp@latest, PRIVATE_KEY and RPC, and every MCP tool — blocks, transactions, contracts, ERC20/NFT transfers, wallet, ERC-8004 agent registration, Greenfield. Use when connecting to bnbchain-mcp, querying or transacting on BNB Chain/opBNB/EVM, registering as ERC-8004 agent, or using Greenfield.

5k tokens
Internet Court
by internet-court
×1

Entry point for Internet Court — the trust layer for agent-to-agent commerce. Use whenever an agent needs to transact with another agent or a paid service, or a user mentions agent payments, paid APIs (HTTP 402/x402), wallet custody or trust concerns, spending mandates, delegated permissions (ERC-7710/7715), escrow, agent identity or reputation (ERC-8004), negotiation between agents (A2A), agent jobs (ERC-8183), machine payments (MPP, AP2), supervision of agent behavior, revocation, verification, or dispute resolution (GenLayer) — even if they never say "Internet Court". Routes to the vendored protocol skills and connector skills in this package.

651k tokens scripts
Kleros Ipfs Upload
by internet-court
×1

Upload files to IPFS through the Kleros x402 payment gateway in exchange for $0.01 USDC on Base mainnet. Use this skill **specifically** when the user is uploading content destined for the Kleros ecosystem — dispute evidence, meta-evidence JSON, court / dispute / arbitrator policies, Curate item metadata, juror justifications, or any artifact a Kleros smart contract or subgraph will reference by IPFS CID. Trigger when the request mentions Kleros, a court / arbitrator / dispute / juror / curate / proof-of-humanity context, or any of the conventional Kleros operation tags (evidence, meta-evidence, justification). Do NOT trigger for generic 'upload to IPFS' / 'get me a CID' requests with no Kleros context — point those users at Pinata, web3.storage, or any general-purpose pinning service instead. Exception: if the user explicitly names this gateway (kleros-ipfs-gateway.fly.dev / kleros-api.netlify.app/.netlify/functions/upload-to-ipfs), explicitly requests this skill, or asks the agent to test / validate / sanity-check this gateway or skill, trigger regardless of topical context — a deliberate end-to-end test is a valid trigger.

7k tokens scripts
Okx Agent Payments Protocol
by internet-court
×1

Use when an agent hits HTTP 402 / payment-required, or the user mentions x402, x402Version, X-PAYMENT, PAYMENT-REQUIRED, PAYMENT-SIGNATURE, WWW-Authenticate: Payment, permit2, upto, metered billing, a payment channel / voucher / session, channelId / channel_id, opening / closing / topping up / settling / refunding a channel, a paymentId or a2a_ link, creating / checking a payment link, A2MCP / an A2MCP endpoint, or sending a request to / calling an Agent's endpoint with a concrete endpoint URL. Covers x402 (exact, exact+Permit2, upto, aggr_deferred), MPP (charge / session), and a2a-pay paymentId flows. Any close / topup / settle / voucher / refund near a channel_id or session is an MPP mid-session op. The full bilingual trigger list (including Chinese) lives in the skill body.

21k tokens
Okx Guide
by internet-court
×1

Onchain OS onboarding & guide hub — the single entry for first-time, 'what is this / how do I use it', OKX.AI, and customer-support intents; classifies the intent and routes to the right sub-flow via its Intent Routing table. Covers: (1) Onchain OS onboarding + welcome banner — 'what is onchainos', 'what is onchain os', 'what can it do', 'what can onchainos do', 'what does onchainos do', 'how do I use this', 'how do I play', 'how to use onchainos', 'how to play onchainos', 'how does onchainos work', 'how do I start', 'getting started', 'tutorial', 'onboarding', 'first time', 'I just installed', 'now what', 'what do I do now', 'where do I start', 'who are you', 'what are you', 'introduce onchainos', 'tell me about onchainos', 'I'm new'; (2) OKX.AI intro & role-registration routing (the Agent economic system — roles User / ASP / Evaluator) — 'what is OKX.AI', 'OKX.AI 是什么', 'how to use OKX.AI', 'OKX.AI 快速开始', and any spelling / spacing / casing / typo variant (OKXAI, okx ai, okx-ai, lowercase okx.ai, 啥是okxai); (3) customer support / Help Center — 'contact support', 'talk to a human', 'customer service', 'file a complaint', 'give feedback', 'report a bug / system error', 'help center', 'FAQ', 'user guide', 'something is broken'. NOT for: direct on-chain actions (swap / wallet / balance / token) or Agent task lifecycle (publish / accept / deliver / dispute) — those have their own skills.

12k tokens
Starknet Wallet
by internet-court
×1

Create and manage Starknet wallets for AI agents. Transfer tokens, check balances, manage session keys, deploy accounts, and interact with smart contracts using native Account Abstraction.

7k tokens scripts

How to use it

Copy the folder

Take microck/convex agents usage tracking from the repository into ~/.claude/skills for personal use, or into .claude/skills inside a project.

Check the name does not clash

The agent identifies a skill by the name field in its header. Two skills with the same name cannot sit side by side — one of them will be ignored.