Skip to content

Instantly share code, notes, and snippets.

@conradcaffier03
Last active July 7, 2026 22:55
Show Gist options
  • Select an option

  • Save conradcaffier03/650cc87f3fd4e41c97afbc04fa14723c to your computer and use it in GitHub Desktop.

Select an option

Save conradcaffier03/650cc87f3fd4e41c97afbc04fa14723c to your computer and use it in GitHub Desktop.
How I measure my AI leverage — the agent-hours tracker (read it from your Claude Code session logs)

How I measure my AI leverage — the agent-hours tracker

Everyone feels 10× more productive with AI. Almost nobody measures it. Here's the exact setup I use to track agent leverage as a live KPI — how many hours my agents work for every hour I put in. Mine moves week to week — recomputed straight from the logs every time, currently around 7:1.

The whole thing reads from data you already have: your Claude Code session logs.

1. Find the data you already have

Claude Code writes a full transcript of every session as JSONL — one event per line, every event timestamped:

ls ~/.claude/projects/*/*.jsonl | head

Each project folder holds its sessions. That timestamp stream is all you need — no extra tooling, no AI estimating anything.

2. Define the two numbers (be strict, or the KPI lies)

  • Agent-active hours = wall-clock the agent actually spent working — from the moment you send a real message to the moment you send your next one, capped per turn (I use 15 min) so a session you walked away from doesn't inflate it.
  • Your input hours = your real hands-on time (prompting, reviewing, deciding). Track it honestly — and exclude anything that isn't this work (for me: study time).

Leverage = agent-active hours ÷ your input hours. If you can't defend both numbers, the ratio is a vanity metric. The cap + the exclusions are what make it honest.

The one trap that quietly breaks this: Claude Code logs tool results (the output of a file read, a bash command, etc.) as type: "user" events too — they're not something you typed. If you count every "user" event as a real message, you reset your "turn" dozens of times during a single burst of agent work and end up measuring almost nothing. The fix: a real message from you has message.content as a plain string; a tool-result "user" event has message.content as an array of tool-result blocks. Only the string ones count.

3. Compute it from the logs

Drop this in leverage.mjs and run node leverage.mjs:

import { readFileSync, readdirSync, statSync } from "node:fs";
import { join } from "node:path";

// Plain recursive walk — no fs.globSync (Node 22+ only, still experimental).
// This runs on any reasonably modern Node.
function findJsonl(dir) {
  let out = [];
  for (const name of readdirSync(dir)) {
    const p = join(dir, name);
    const st = statSync(p);
    if (st.isDirectory()) out = out.concat(findJsonl(p));
    else if (name.endsWith(".jsonl")) out.push(p);
  }
  return out;
}

const CAP_MS = 15 * 60 * 1000;               // cap per turn so idle gaps don't inflate
const files = findJsonl(`${process.env.HOME}/.claude/projects`);

let agentMs = 0;
for (const f of files) {
  const lines = readFileSync(f, "utf8").trim().split("\n").filter(Boolean);
  let turnStart = null, turnEnd = null;
  for (const line of lines) {
    let e;
    try { e = JSON.parse(line); } catch { continue; }   // skip any malformed line
    const t = new Date(e.timestamp).getTime();
    if (Number.isNaN(t)) continue;

    // Real message from you = type "user" AND content is a plain string.
    // Tool results are also type "user" but content is an array — not a real turn.
    const isRealUserMessage = e.type === "user" && typeof e.message?.content === "string";

    if (isRealUserMessage) {
      if (turnStart !== null) agentMs += Math.min(turnEnd - turnStart, CAP_MS);
      turnStart = t; turnEnd = t;                 // start the next turn
    } else if (turnStart !== null) {
      turnEnd = t;                                 // extend the current turn
    }
  }
  if (turnStart !== null) agentMs += Math.min(turnEnd - turnStart, CAP_MS);
}

const agentHours = agentMs / 3.6e6;
const myHours = Number(process.argv[2] ?? 14);      // your tracked input hours this week
console.log(`agents: ${agentHours.toFixed(1)}h  ·  me: ${myHours}h  ·  leverage ${(agentHours / myHours).toFixed(1)} : 1`);
node leverage.mjs 14      # 14 = your input hours this week
# → agents: 98.0h · me: 14h · leverage 7.0 : 1

That's the number. Measured, not guessed.

4. Scope it to a window (weekly reads best)

A lifetime ratio is meaningless. Filter events to the last 7 days so the KPI moves and stays comparable week to week:

const WEEK_AGO = Date.now() - 7 * 864e5;
// inside the loop: if (t < WEEK_AGO) continue;

Run it on a cron (or a Claude Code /leverage skill) so it recomputes on its own.

5. Make it live (optional, but it's the proof)

Write the result to a tiny JSON your site can read, and recompute on a schedule:

import { writeFileSync } from "node:fs";
writeFileSync("leverage.json", JSON.stringify({ ratio: +(agentHours/myHours).toFixed(1), agentHours, myHours, ts: Date.now() }));

Point a small widget at leverage.json. Now the number on your page moves every time you work — it's a screenshot nobody can fake, because it isn't a screenshot.


The honest caveats (so you don't kid yourself)

  • The cap is a judgment call — pick one and keep it fixed.
  • "Agent-active" ≠ "productive". Lots of agent-hours on the wrong thing is still wasted time. Pair this with an outcome metric (shipped features, not just hours).
  • Your input hours are self-reported. The exclusions (study, dabbling) are what keep you honest.
  • Double-check the tool-result trap above — it's the single easiest way to quietly get this number wrong in either direction.

This tracker measures your leverage. The reason mine keeps climbing is the setup behind it — a CLAUDE.md + skills + memory system that makes Claude remember your whole project and run like a senior dev instead of forgetting everything every session. That's the Operator Setup: https://get.caffier-agency.com

New to this? Start free with the 7-Day Operator Challenge (same link) — it builds the foundation. The live leverage-tracking above is part of the paid guide, not the free challenge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment