Agent discovery
Find every agent in your codebase
You can find them by reading the source, not by waiting for the bill. Static analysis parses your Python, finds every provider call site and every LangGraph node, and returns an inventory of the agents your code actually contains. We ran exactly this across 133 public agent repositories and found 293 agents — a median of 2 per repository, and 8 at the 90th percentile. The distribution has a long tail: the largest repository in the sample contained 91.
Why you don't know how many agents you have
Because nothing in the stack is organised around agents. A provider bills an API key. A framework instantiates whatever the code asks for. An agent is a pattern in your source — a decorated function, a graph node, a class with a run method — and no runtime system enumerates them for you.
So the count drifts. Somebody adds a summariser inside a retrieval step. A coordinator gains a fourth delegate. A prototype agent stays wired into production because removing it was nobody’s task. None of it fails, and none of it shows up as a line item — it shows up as one number on an invoice that went up.
How the scan works
It is read-only static analysis. The scanner parses each Python file with libcst — a concrete syntax tree, so it sees the code as written rather than as executed — and looks for two things.
Direct provider call sites: Anthropic messages.create and stream, OpenAI chat.completions.create and embeddings.create, Google generate_content. Each match is gated on the file actually importing that provider, which is what keeps a variable called client from producing a false positive.
LangGraph nodes: registered through add_node where the handler is a module-level function. One candidate per enclosing function, so a node that makes three calls counts once.
Nothing executes and nothing is sent anywhere. The bound worth knowing: in the 133-repository sample, 82 repositories reached providers only through a framework and so had no directly scannable calls at all. Frameworks that route internally — CrewAI goes through litellm — are the current blind spot, and closing it is the top item on the scanner backlog.
What it finds
Two things, and the second is usually the surprise. The first is the inventory: how many agents, where they are, and what each one calls. The second is cost defects — patterns that make the same behaviour cost more than it needs to.
In the public sample, 76% of the 51 repositories making direct LLM calls carried at least one. System prompts assembled by interpolation, which defeat provider prefix caching unless deliberately structured, appeared at 347 call sites across 39 repositories. One in ten repositories ran a frontier model inside its test suite.
The full method, per-finding numbers and limitations are published, along with the raw per-repository output.
Attributing cost to what it finds
Discovery tells you the agents exist. Attribution tells you what each one costs, and that runs in your own process: one init() call at startup, then a decorator naming each agent.
import capsera
from anthropic import Anthropic
capsera.init(api_key="cap-...") # once, at startup
client = Anthropic()
@capsera.agent(name="research_agent", team="growth")
def research(question: str) -> str:
# Every provider call made inside this function — and inside anything
# it calls — is attributed to research_agent. Sub-agents spawned at
# runtime inherit the chain; nothing is passed per call.
return client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
messages=[{"role": "user", "content": question}],
).content[0].textThe decorator is the only per-agent step. Everything called underneath it inherits the identity from a thread-local context stack, which is why a sub-agent created at runtime is attributed without anyone provisioning a key or passing a tag on the call. That inheritance is the part a gateway cannot reproduce: at a proxy, every request has to carry its own tag, and a flat tag cannot express “this agent, inside this run, inside this team”.
Shipping next: connect a repository and Capsera opens a pull request that adds those decorators for you, using the same scanner and naming logic behind the benchmark. Until it lands, the decorators are a few lines you add once per agent.
What it never sees
Not your prompts, and not your model’s replies. The scan reads source code and returns structure; the SDK sends token counts, model names, costs, latency and the identifiers you attach. Prompt analysis, when enabled, extracts a one-way hash of the system prompt plus counts — never the text.
This is a design property rather than a policy promise: the extraction happens inside your process, so prompt and completion content has no path to our servers. It is also why Capsera cannot show you a trace of what an agent said, and why diagnosing cost rather than debugging behaviour is the job it does.
Questions this page answers
- How do I find every agent in my codebase?
- Static analysis over the source, rather than waiting for runtime traffic to reveal them. Capsera's scanner parses Python with libcst and looks for direct provider call sites — Anthropic messages.create, OpenAI chat.completions.create and embeddings.create, Google generate_content — gated on the file actually importing that provider, plus LangGraph nodes registered through add_node. We ran exactly this across 133 public agent repositories: it found 293 agents, a median of 2 per repository and 8 at the 90th percentile.
- How do I know if an agent is running untracked?
- Compare the agents your code contains against the agents your dashboard has seen spend from. Static analysis gives you the first list without running anything; attributed events give you the second. Anything in the first list and not the second is either dead code or spending money you cannot see, and both are worth knowing about.
- How do I get per-agent cost visibility without changing my code?
- Discovery needs no changes at all — the scan is read-only static analysis over source. Attribution today needs a small change: one capsera.init() call at startup, plus a decorator or context manager naming each agent, after which sub-agents inherit their parent's identity automatically. Removing that step is what the repo-connect flow and the instrumentation pull request are for, and both are the next thing shipping.
- How do I instrument all my agents automatically?
- Today you add one init() call and a decorator per agent, and the SDK patches Anthropic, OpenAI and Google Gemini clients in place — sync and async — so every call underneath is attributed without touching individual call sites. Shipping next: connect a repository and Capsera opens a pull request that adds those decorators for you, using the same scanner and naming logic that produced the benchmark. Until that lands, the decorators are a few lines you add once per agent.