AgentLens
Framework-agnostic observability, debugging, and deterministic issue detection for AI agents.
AgentLens captures what your agents actually do, normalizes it into a universal event model, and helps you find out why a run went wrong — with deterministic, offline detectors you run on demand.
What is AgentLens?
AgentLens is a local, framework-agnostic library for recording and inspecting AI agent activity. Concretely, it:
- Records agent activity (tool calls, LLM calls, decisions, errors) as an ordered stream of events on a run.
- Stores runs, events, and issues — in memory by default, or in a local SQLite database.
- Detects issues explicitly. Nothing runs automatically in the background;
you call
lens.detect(run_id)when you want deterministic detectors (loops, excessive retries, duplicate tool calls, inefficiencies) analysed. - Produces reports that aggregate a run with its events and issues, and exports them as deterministic JSON or Markdown strings.
- Provides a read-only CLI for inspecting a persisted database.
- Provides optional framework adapters (LangChain, the OpenAI Agents SDK, CrewAI) that record activity from those frameworks into an AgentLens run using the same public API application code uses.
AgentLens does not automatically fix agents, does not call any model or API on your behalf, and does not run anything in the background. Detection is deterministic and explicit; there is no LLM-based analysis in the current version (see Why AgentLens? for that as a design principle, not a shipped feature).
Project layout
| Path | Purpose |
|---|---|
agentlens/core/ |
Core runtime: AgentLens, trace lifecycle, run manager |
agentlens/models/ |
Pydantic models for the universal event model |
agentlens/detectors/ |
Deterministic failure detectors |
agentlens/storage/ |
Persistent storage (SQLiteTraceStore) |
agentlens/export/ |
JSON / Markdown report export |
agentlens/cli/ |
The read-only agentlens command-line tool |
agentlens/integrations/ |
Optional framework adapters (LangChain, OpenAI Agents SDK, CrewAI) |
tests/unit/ |
Fast, isolated unit tests |
tests/integration/ |
Cross-component integration tests |
examples/ |
Runnable, offline usage examples |
scripts/ |
Release-readiness / clean-install verification |
Why AgentLens?
Agent frameworks (LangChain, CrewAI, the OpenAI Agents SDK, and others) each emit their own traces in their own shapes. AgentLens sits underneath them as a common, framework-agnostic layer: adapters translate each framework's native events into one universal event model, so the same storage, detectors, reports, CLI, and export logic work no matter which framework — or no framework at all — produced the run. See Architecture for the exact dependency flow.
Principles
- Works locally with minimal infrastructure — SQLite, no cloud dependencies, no network calls made by AgentLens itself.
- The core is framework-agnostic; frameworks integrate through optional adapters that depend on AgentLens, never the other way around.
- All framework-specific data is converted into one universal internal event model before anything else touches it.
- Detection is deterministic and explicit today. The design leaves room for optional LLM-based analysis to complement (not replace) the deterministic detectors later, but that analysis is not implemented in the current version — nothing in this repository calls a model.
- Modular, testable code with automated tests for every significant feature, enforced in CI on every push and pull request (see Supported Python Versions).
Installation
The PyPI distribution name is agentlens-evaluator (the agentlens name
was already taken by an unrelated package); the Python import name is
unaffected and remains agentlens. As of this writing the package has not
yet been published, so pip install agentlens-evaluator will not work until
a release is made — install from a local clone in the meantime (see below).
Once published, installation will be:
pip install agentlens-evaluator
from agentlens import AgentLens # import name is unchanged
Core (from source, until the first PyPI release)
git clone https://github.com/dhananjaiyadav1234/Agent-lens.git
cd Agent-lens
python -m venv .venv
source .venv/bin/activate # .venv\Scripts\activate on Windows
pip install --upgrade pip
pip install .
Verify the install:
python -c "from agentlens import AgentLens; print('AgentLens OK')"
agentlens --help
Core installation depends on only pydantic — no framework, no network
client, no optional dependency is required or imported.
Optional framework integrations
Each framework adapter is an install extra; none is required for core usage. From PyPI (once published):
pip install "agentlens-evaluator[langchain]"
pip install "agentlens-evaluator[openai-agents]"
pip install "agentlens-evaluator[crewai]"
From a local clone today, the same extras apply to the dot-path form:
pip install ".[langchain]"
pip install ".[openai-agents]"
pip install ".[crewai]"
Extras can be combined:
pip install ".[langchain,openai-agents,crewai]"
Development install
pip install -e ".[dev]"
See Development / Testing for running the test suite and linter, and Framework Integrations for adapter usage.
Quick Start
This is the complete basic workflow, using only core AgentLens APIs — no framework, no network, no API key:
from agentlens import AgentLens
from agentlens.models import EventType
from agentlens.export import export_json, export_markdown
lens = AgentLens()
with lens.trace("Look up a customer's order status") as trace:
trace.record_event(
event_type=EventType.TOOL_CALL_STARTED,
name="lookup_customer",
input={"customer_id": "123"},
)
trace.record_event(
event_type=EventType.TOOL_CALL_COMPLETED,
name="lookup_customer",
input={"customer_id": "123"},
output={"open_orders": 2},
status="ok",
)
run_id = trace.run_id
issues = lens.detect(run_id) # explicit: detectors never run on their own
report = lens.get_report(run_id) # explicit: aggregates the run + events + issues
print(report.summary.total_events) # 4 (RUN_STARTED, 2 tool events, RUN_COMPLETED)
print(export_json(report)) # deterministic JSON string
print(export_markdown(report)) # deterministic Markdown string
This exact workflow is exercised by tests/unit/test_readme_quickstart.py, so
it will not silently drift from what's documented here.
Detecting Issues
Recording activity never automatically runs detectors. record_event only
appends an event to the run; nothing analyses it until you explicitly call
lens.detect(run_id). This is true with and without a framework integration —
adapters record events the same way application code does, and detection stays
a separate, explicit step you control.
Detectors are pure analysis components: given an AgentRun and its ordered
AgentEvent list they return validated AgentIssue objects. They depend only
on the universal models — never on AgentLens, storage, or a framework — so
they run on AgentLens traces, imported JSON traces, and framework-adapter
traces alike.
LoopDetector
Identifies pathological repeated execution cycles. A loop is a contiguous run
of one or more logically-equivalent events repeated consecutively at least
minimum_repetitions times — A B A B A B is cycle_length=2, repetitions=3.
- Events are compared by a fingerprint of
event_type+name+ canonical JSON ofinputonly.output,status,duration_ms,metadata, ids, and timestamps are ignored, so cycles that vary only in, say,metadata.iterationstill match. - Events are analysed in
sequence_numberorder; the caller's list is not modified. RUN_STARTED/RUN_COMPLETEDare excluded.ERRORevents break a cycle — repeated failure/retry sequences are a retry pattern and get their own detector, not a loop.- Default
minimum_repetitionsis 3 (must be anint≥ 2). - Severity by repetition count: 3 → MEDIUM, 4–5 → HIGH, 6+ → CRITICAL.
from agentlens import AgentLens
from agentlens.detectors import LoopDetector
from examples.demo_agents import run_looping_agent
lens = AgentLens()
run_id = run_looping_agent(lens)
run = lens.get_run(run_id)
events = lens.get_events(run_id)
issues = LoopDetector().detect(run, events)
# -> one AgentIssue: issue_type=agent_loop, severity=medium,
# metadata={"detector": "loop_detector", "cycle_length": 2,
# "repetitions": 3, "pattern": [...]}
python -m examples.detect_loops runs the detector against every demo scenario.
RetryDetector
Identifies excessive retries of the same tool operation. A failed attempt is
a TOOL_CALL_STARTED immediately followed (in sequence_number order) by an
associated ERROR; a retry streak is two or more consecutive failed
attempts of the same operation. A streak that reaches minimum_failed_attempts
produces one issue describing those failures.
- Operation identity = tool
name+ canonical JSON ofinput(key order irrelevant; list order and primitive types significant).output,status,metadata, ids, and timestamps are ignored. - Error association: if
ERROR.outputis a mapping with a string"operation", it must equal the active tool name; if"operation"is absent, the adjacentERRORis treated as associated; a mismatch breaks the streak. - Any unrelated event — a different operation, a
DECISION, a success, a lifecycle boundary — breaks the streak. Streaks never spanRUN_STARTED/RUN_COMPLETED. - A successful final attempt (
TOOL_CALL_STARTED→TOOL_CALL_COMPLETED) ends the streak but does not erase the preceding failures: they are still reported, and the successful attempt is not amongrelated_event_ids. - Default
minimum_failed_attemptsis 3 (must be anint≥ 2). - Severity by failed-attempt count: 2–3 → MEDIUM, 4–5 → HIGH, 6+ → CRITICAL.
from agentlens import AgentLens
from agentlens.detectors import RetryDetector
from examples.demo_agents import run_retrying_agent
lens = AgentLens()
run_id = run_retrying_agent(lens)
issues = RetryDetector().detect(lens.get_run(run_id), lens.get_events(run_id))
# -> one AgentIssue: issue_type=excessive_retry, severity=medium,
# metadata={"detector": "retry_detector", "operation": "fetch_customer",
# "failed_attempts": 3, "input": {"customer_id": "123"}}
Detection is deterministic and fully offline.
python -m examples.detect_retries runs it against every demo scenario.
DuplicateToolDetector
Identifies redundant repeated tool calls: a second (or later) successful execution of the same operation with no new information justifying the redo.
- Same operation = identical tool
name+ canonical JSON ofinput(key order irrelevant, recursively; list order and primitive types significant). - Successful call = a
TOOL_CALL_STARTEDimmediately followed (insequence_numberorder) by a matchingTOOL_CALL_COMPLETED. ADECISION,ERROR, or secondTOOL_CALL_STARTEDin between invalidates the pair. Failed attempts areRetryDetector's concern, not this one. related_event_ids= exactly four, in trace order: the originalTOOL_CALL_STARTED/TOOL_CALL_COMPLETEDand the duplicateTOOL_CALL_STARTED/TOOL_CALL_COMPLETED. No lifecycle or decision events.- Information boundary — resets what counts as "already done": a successful
TOOL_CALL_COMPLETEDfor a different operation, anLLM_CALL_COMPLETED, an event whosemetadata/outputsetsuses_new_information/depends_on_new_information/new_informationtotrue, or a malformed completion. A plainDECISION, an explicituses_new_information: false, lifecycle events, andERRORevents do not reset it. - Severity by the duplicate's ordinal in its region: 1st → MEDIUM, 2nd–3rd → HIGH, 4th+ → CRITICAL. One issue per duplicate call; each names the original and that specific duplicate.
from agentlens import AgentLens
from agentlens.detectors import DuplicateToolDetector
from examples.demo_agents import run_duplicate_tool_agent
lens = AgentLens()
run_id = run_duplicate_tool_agent(lens)
issues = DuplicateToolDetector().detect(lens.get_run(run_id), lens.get_events(run_id))
# -> one AgentIssue: issue_type=duplicate_tool_call, severity=medium,
# metadata={"detector": "duplicate_tool_detector", "operation": "lookup_customer",
# "input": {"customer_id": "123"}, "original_sequence_number": ...,
# "duplicate_sequence_number": ..., "duplicate_count": 1}
Detection is deterministic and fully offline.
python -m examples.detect_duplicate_tools runs it against every demo scenario.
InefficiencyDetector
Flags a successful tool operation only when the trace carries explicit structured evidence that it was unnecessary or wasteful. Nothing is inferred from timing, durations, record counts, decision names, or semantics.
- Successful operation = a
TOOL_CALL_STARTEDimmediately followed by a matchingTOOL_CALL_COMPLETED(same name; same canonical input when the completed event carries one). ADECISION/ERROR/second start in between invalidates the pair; failed attempts are never "successful work". - Evidence signals (each read from event
metadataor a mappingoutput, booleans only —"true"/1do not count):scope_mismatch— the operation's own event hasrequired_scopeandactual_scopeboth present, same JSON type, unequal (no ordering assumed);explicit_wasted_work—wasted_work is Trueon the operation's own event;explicit_unnecessary—necessary is Falseon the operation's own event;explicit_wasted_operation— a later event names it viawasted_operation == "<op name>", associated with the most recent preceding successful operation of that name;sufficient_information_already_available— an earlier event setsufficient_to_answer is True. This never flags an operation on its own; it only raises severity of one already flagged by the four signals above.
related_event_ids(UUIDs, unique, trace order, no lifecycle events): the operation'sTOOL_CALL_STARTEDandTOOL_CALL_COMPLETED, plus the recognition event and the sufficiency event when those signals are used.- Severity by number of distinct signals: 1 → MEDIUM, 2 → HIGH, 3+ → CRITICAL.
- One issue per inefficient operation; multiple issues are returned in trace order. The detector is parameterless, deterministic, holds no state between calls, and is fully offline.
from agentlens import AgentLens
from agentlens.detectors import InefficiencyDetector
from examples.demo_agents import run_inefficient_agent
lens = AgentLens()
run_id = run_inefficient_agent(lens)
issues = InefficiencyDetector().detect(lens.get_run(run_id), lens.get_events(run_id))
# -> one AgentIssue: issue_type=inefficiency, severity=critical,
# metadata={"detector": "inefficiency_detector", "operation": "fetch_all_customers",
# "input": {}, "evidence": ["scope_mismatch", "explicit_wasted_operation",
# "sufficient_information_already_available"]}
python -m examples.detect_inefficiencies runs it against every demo scenario.
Running every detector
AgentLens.detect(run_id) runs all four deterministic detectors over one stored
run and returns the combined list[AgentIssue] — no need to import and invoke
each detector by hand. It looks the run and its events up in the instance's
store (so only that run's events are analysed), then runs the detectors in a
fixed order — loop, retry, duplicate-tool, inefficiency — with their default
configuration. Issues come back in that detector order; within a detector its own
trace ordering is preserved. It is a pure analysis call: issues are not
persisted unless you're using a store that persists them (see
Persistence), and neither the run nor its events are modified.
An unknown run id raises AgentLensError.
from agentlens import AgentLens
from examples.demo_agents import run_inefficient_agent
lens = AgentLens()
run_id = run_inefficient_agent(lens)
issues = lens.detect(run_id) # or lens.detect(run)
The same composition is available framework-agnostically for traces that did not
come from a live AgentLens:
from agentlens.detectors import run_detectors
issues = run_detectors(run, events)
python -m examples.detect_all_issues runs lens.detect over every demo scenario.
Reports and Export
lens.get_report(run_id) returns an AgentReport that aggregates one stored run
with its events and its already-persisted issues, plus a deterministic
ReportSummary:
report = lens.get_report(run_id)
report.run # the stored AgentRun
report.events # its events, in sequence_number order
report.issues # its persisted issues, in save order (no de-duplication)
report.summary.total_events # len(report.events), lifecycle events included
report.summary.total_issues # len(report.issues)
report.summary.events_by_type # {EventType: count}, first-appearance order
report.summary.issues_by_type # {IssueType: count}, first-appearance order
report.summary.issues_by_severity # {Severity: count}, first-appearance order
detect(run_id) is analysis (+ issue persistence when the store supports it);
get_report(run_id) is read-only retrieval + deterministic aggregation — it
never runs detectors, creates issues, or writes storage, and repeated calls on
unchanged data return byte-identical content. An unknown run id raises
AgentLensError. It works identically with the in-memory store and
SQLiteTraceStore, including after the database is closed and reopened.
python -m examples.report_generation runs a scenario, detects, and prints its
report.
Export
agentlens.export turns a built AgentReport into a string — JSON or Markdown.
Both functions operate purely on an AgentReport and return a str; neither
writes a file:
from agentlens.export import export_json, export_markdown
report = lens.get_report(run_id)
json_report = export_json(report) # str
markdown_report = export_markdown(report) # str
- Read-only. The exporters take an
AgentReport, not a store orAgentLens. They never run detectors, never persist anything, and never mutate the report. - Deterministic.
export_jsonisjson.dumps(report.model_dump(mode="json"), sort_keys=True, indent=2, ensure_ascii=False)— repeated calls are byte-identical andjson.loadsof the result equalsreport.model_dump(mode="json"). - Ordering preserved.
export_markdownrenders events inreport.eventsorder, issues inreport.issuesorder, and each summary table in the report's existing first-appearance key order — never sorted. Its sections are always Run → Summary → Events → Issues; empty issues render_No issues detected._. - No file-writing API exists in either function — callers write the returned string to a file themselves if they want one.
python -m examples.export_report traces a scenario, detects, builds a report,
and prints both exports.
Command Line
Installing the package provides a read-only agentlens command for inspecting
a persisted SQLite database. It is a thin adapter over the Python API — every
command opens the store, reads, prints, and closes; no command runs
detectors or writes to the database (there is deliberately no detect
subcommand — run detection through the Python API and persist it to SQLite,
then inspect it with the CLI).
agentlens runs --db agentlens.db
agentlens issues <run-id> --db agentlens.db
agentlens report <run-id> --db agentlens.db
agentlens export <run-id> --format json --db agentlens.db
agentlens export <run-id> --format markdown --db agentlens.db
(Without installing the console script, python -m agentlens.cli.main <args>
is equivalent.)
--db PATHis always explicit — there is no environment variable, config file, or default database location.- All commands are read-only: they use the persisted runs, events, and issues and never write to the database.
issuesshows already-persisted issues — it does not run detection.reportandexportbuild the report vialens.get_report(...)and likewise run no detectors.- To (re)compute and persist issues, run detection separately through the Python
API:
lens.detect(run_id)against aSQLiteTraceStore-backedAgentLens. reportprints the same Markdown asexport --format markdown;export --format jsonprints exactlyexport_json(report). Nothing is added around the export output.- Exit codes:
0on success (including "no runs"/"no issues"),1on an expected error (invalid run id, unknown run),2for argument errors.
python -m examples.cli_usage drives every command against a throwaway database.
Framework Integrations
Three optional adapters record activity from a specific framework into an
existing AgentLens run as ordinary AgentEvents, using the same public
AgentLens.record_event API application code uses. Each is:
- optional — none is a core dependency; core usage never imports any of
them (verified by
tests/unit/test_packaging.py); - record-only — none of them ever calls
lens.detect(...),run_detectors,save_issues, orlens.get_report(...). Detection and reporting stay explicit steps you call yourself, exactly as in the Quick Start; - offline — each only observes data the framework hands it; none makes a network or provider call or reads an environment variable.
LangChain
pip install ".[langchain]"
from agentlens import AgentLens
from agentlens.integrations.langchain import AgentLensCallbackHandler
lens = AgentLens()
with lens.trace("Answer a customer support question") as trace:
handler = AgentLensCallbackHandler(lens=lens, run_id=trace.run_id)
chain.invoke(
{"question": "Where is my order?"},
config={"callbacks": [handler]},
)
issues = lens.detect(trace.run_id) # detection is explicit
report = lens.get_report(trace.run_id) # reporting is explicit
Attach the handler inside the run's with lens.trace(...) block; it writes
every event to that one run and never creates another. Tool
starts/completions/errors map to TOOL_CALL_STARTED / TOOL_CALL_COMPLETED /
ERROR; LLM activity to LLM_CALL_STARTED / LLM_CALL_COMPLETED / ERROR;
agent actions/finishes to DECISION. Each event carries
metadata={"framework": "langchain", "langchain_run_id": "..."}.
Importing agentlens (or the CLI, or the export layer) does not import
LangChain. Importing agentlens.integrations.langchain without the extra
raises a clear ModuleNotFoundError telling you to install
agentlens[langchain].
python -m examples.langchain_integration runs a fully offline end-to-end demo.
OpenAI Agents SDK
pip install ".[openai-agents]"
from agents import Runner
from agents.tracing import add_trace_processor
from agentlens import AgentLens
from agentlens.integrations.openai_agents import AgentLensOpenAITracer
lens = AgentLens()
with lens.trace("Answer a customer support question") as trace:
tracer = AgentLensOpenAITracer(lens=lens, run_id=trace.run_id)
add_trace_processor(tracer)
Runner.run_sync(agent, "Where is my order?")
issues = lens.detect(trace.run_id) # detection is explicit
report = lens.get_report(trace.run_id) # reporting is explicit
AgentLensOpenAITracer is a TracingProcessor registered through the SDK's own
official tracing extension mechanism (agents.tracing.add_trace_processor) —
register it inside the run's with lens.trace(...) block. It adopts the
OpenAI trace(s) started while that run is the lens's active trace, so several
globally-registered tracers never leak spans between AgentLens runs. function
spans → TOOL_CALL_STARTED / TOOL_CALL_COMPLETED (or ERROR on a span
error); generation / response spans → LLM_CALL_STARTED /
LLM_CALL_COMPLETED / ERROR; handoff spans → DECISION. agent,
guardrail, custom, and other span types are deliberately not mapped, to
keep the trace analysis-friendly. Each event carries
metadata={"framework": "openai_agents", "framework_run_id": "...", "framework_span_id": "..."}.
Importing agentlens does not import the SDK; importing
agentlens.integrations.openai_agents without the extra raises a clear
ModuleNotFoundError.
python -m examples.openai_agents_integration runs a fully offline end-to-end demo.
CrewAI
pip install ".[crewai]"
from crewai import Crew
from agentlens import AgentLens
from agentlens.integrations.crewai import AgentLensCrewAIListener
lens = AgentLens()
with lens.trace("Research a topic") as trace:
with AgentLensCrewAIListener(lens=lens, run_id=trace.run_id):
crew.kickoff()
issues = lens.detect(trace.run_id) # detection is explicit
report = lens.get_report(trace.run_id) # reporting is explicit
AgentLensCrewAIListener is a BaseEventListener on CrewAI's public
crewai_event_bus — CrewAI's official extension point. It must be used as a
context manager while the AgentLens trace is active, exactly as shown above:
CrewAI dispatches its event handlers from background worker threads, so the
listener buffers a JSON-safe copy of each event as it arrives and replays those
events into AgentLens — in CrewAI's own emission order — on your thread, when
the with AgentLensCrewAIListener(...) block exits. It records only activity
emitted while that run is the lens's active trace, so several listeners
registered on the global bus never leak events between AgentLens runs.
Tool events map to TOOL_CALL_STARTED / TOOL_CALL_COMPLETED (or ERROR on a
tool failure); LLM call events to LLM_CALL_STARTED / LLM_CALL_COMPLETED /
ERROR; agent-execution and task failures to ERROR. Crew / task / agent
lifecycle events (kickoff, task/agent started/completed, and similar) are
deliberately not mapped, to keep the trace analysis-friendly. CrewAI exposes
no public "agent decided X" event in the tested version, so this integration
does not currently produce any DECISION events — this is a documented gap,
not a promise of full lifecycle coverage. Each event carries
metadata={"framework": "crewai", "framework_event_type": "...", "framework_event_id": "..."}.
Importing agentlens does not import CrewAI; importing
agentlens.integrations.crewai without the extra raises a clear
ModuleNotFoundError.
python -m examples.crewai_integration runs a fully offline end-to-end demo.
Persistence
By default an AgentLens keeps runs and events in memory only — AgentLens()
behaves exactly as before. To keep them across process restarts, pass a
SQLiteTraceStore with an explicit database path:
from agentlens import AgentLens
from agentlens.storage import SQLiteTraceStore
store = SQLiteTraceStore("agentlens.db")
lens = AgentLens(store=store)
with lens.trace("example task") as trace:
trace.record_event(...)
run_id = trace.run_id
store.close()
The database (and its schema) is created automatically.
Issue persistence
lens.detect(run_id) does two things when the underlying store supports it: it
runs the detectors and persists the issues it generates. lens.get_issues(run_id)
reads back what was persisted — it never re-runs detection.
lens = AgentLens(store=SQLiteTraceStore("agentlens.db"))
issues = lens.detect(run_id) # runs detectors, stores + returns issues
persisted = lens.get_issues(run_id) # reads stored issues; no detection
# issues == persisted
- Issue storage is append-only and not de-duplicated.
AgentIssue.idis generated fresh each detection, so callingdetectagain appends a second batch of issue records —len(lens.get_issues(run_id))grows. There is no "replace previous results" behaviour. - Detector content (type, severity, description, metadata, related event ids,
detector order) is deterministic across repeated
detectcalls; only theAgentIssue.ids differ. get_issuesreturns[]for an unknown run or a run that has never been detected.- All of
AgentRun,AgentEvent, andAgentIssueare retrieved back through Pydantic validation, so they are semantically identical to what was stored; event order is bysequence_number, matching the in-memory store. SQLiteTraceStoreuses the standard library'ssqlite3— no new dependency. Callstore.close()when done, or use it as a context manager. It is not thread-safe, and there is no environment-variable or implicit-path config.
python -m examples.sqlite_persistence traces a run, closes the database,
reopens it, and detects issues. python -m examples.persisted_issues detects
into SQLite, reopens the database, and reads the issues back without re-running
detection.
Architecture
Dependency direction is one-way, top to bottom. Nothing below a layer imports anything above it:
Framework integrations (LangChain, OpenAI Agents SDK, CrewAI)
│ record events via the public AgentLens API
▼
AgentLens (agentlens.core)
│ opens/closes traces, assigns sequence numbers
▼
Tracing / Events (AgentRun, AgentEvent — agentlens.models)
│
▼
Storage (in-memory, or SQLiteTraceStore — agentlens.storage)
│
▼
Explicit Detection (agentlens.detectors — you call lens.detect(...))
│ produces
▼
Issues (AgentIssue, persisted alongside the run)
│ aggregated into
▼
Reports (AgentReport — agentlens.core / agentlens.models)
│
▼
Export / CLI (agentlens.export, agentlens.cli — read-only)
- Framework adapters record events. They call the same
AgentLens.record_event(orTrace.record_event) application code calls; they never touch storage, detectors, or reports directly, and none of them imports another adapter. - Detectors analyse explicitly. They only run when you call
lens.detect(...)orrun_detectors(...); nothing inAgentLens.record_eventor any adapter triggers them. - Reports aggregate existing data.
get_reportreads back the run, its events, and its already-persisted issues — it computes a summary but performs no analysis of its own. - Exports serialize reports.
export_json/export_markdowntake anAgentReportand return a string; they have no dependency onAgentLens, storage, or detectors. - The CLI reads persisted data. Every subcommand opens a
SQLiteTraceStore, calls the sameAgentLensread methods application code would, and prints — it never writes.
Examples
Every example under examples/ is offline, deterministic, and free of API
keys, network calls, and environment-variable configuration (verified in this
milestone by running each one and grepping for network/secret usage).
| Example | Demonstrates |
|---|---|
run_demo_scenarios.py |
Core tracing: five controlled scenarios via the real AgentLens API |
detect_loops.py |
LoopDetector |
detect_retries.py |
RetryDetector |
detect_duplicate_tools.py |
DuplicateToolDetector |
detect_inefficiencies.py |
InefficiencyDetector |
detect_all_issues.py |
lens.detect(run_id) — all four detectors together |
report_generation.py |
Building an AgentReport |
export_report.py |
export_json and export_markdown |
sqlite_persistence.py |
SQLiteTraceStore close/reopen |
persisted_issues.py |
Issue persistence and read-back without re-detecting |
cli_usage.py |
Every agentlens CLI command against a throwaway database |
langchain_integration.py |
AgentLensCallbackHandler |
openai_agents_integration.py |
AgentLensOpenAITracer |
crewai_integration.py |
AgentLensCrewAIListener |
Run any of them with python -m examples.<name> (see each section above for
the exact command).
Supported Python Versions
AgentLens targets Python 3.11 and 3.12. Both are verified by the CI matrix
(.github/workflows/ci.yml) on every push and pull request: the full test
suite and ruff check / ruff format --check must pass on both interpreters.
Development / Testing
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
Run the tests:
pytest
Run the linter and formatter check:
ruff check .
ruff format --check .
To verify the package the way an external user would — a clean virtual
environment, a real pip install ., the console script, and each optional
extra in isolation — run:
scripts/verify_release.sh
It builds a throwaway venv (removed on exit), never touches your development environment, and exits non-zero on the first failed check.
License
Apache-2.0.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file agentlens_evaluator-0.1.1.tar.gz.
File metadata
- Download URL: agentlens_evaluator-0.1.1.tar.gz
- Upload date:
- Size: 130.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a4e6e64c46efabf3dd5a9c6c6e2634827e6b81deb92515d147d47cab56da13b7
|
|
| MD5 |
23a258d54ecb8b7631fbe80ff22ed618
|
|
| BLAKE2b-256 |
c91efa999ed54216e041dd095a775929880d32101ce61d8698c41a6351b327e5
|
Provenance
The following attestation bundles were made for agentlens_evaluator-0.1.1.tar.gz:
Publisher:
release.yml on dhananjaiyadav1234/Agent-lens
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
agentlens_evaluator-0.1.1.tar.gz -
Subject digest:
a4e6e64c46efabf3dd5a9c6c6e2634827e6b81deb92515d147d47cab56da13b7 - Sigstore transparency entry: 2724535757
- Sigstore integration time:
-
Permalink:
dhananjaiyadav1234/Agent-lens@ec77f8896cfc4e159a2ae1646db9fdca9f8fee7f -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/dhananjaiyadav1234
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@ec77f8896cfc4e159a2ae1646db9fdca9f8fee7f -
Trigger Event:
release
-
Statement type:
File details
Details for the file agentlens_evaluator-0.1.1-py3-none-any.whl.
File metadata
- Download URL: agentlens_evaluator-0.1.1-py3-none-any.whl
- Upload date:
- Size: 72.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e5d94d2fe6a9fb379505b06842d74073db368e9bd2ea74cb730179aef250558b
|
|
| MD5 |
a8d3103b92eb48f1ae7cf071c7399f05
|
|
| BLAKE2b-256 |
dd75d35a3ac7abedd992d3547a8c3ef6185ff9e13dcd436f621018bc36cb3ecf
|
Provenance
The following attestation bundles were made for agentlens_evaluator-0.1.1-py3-none-any.whl:
Publisher:
release.yml on dhananjaiyadav1234/Agent-lens
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
agentlens_evaluator-0.1.1-py3-none-any.whl -
Subject digest:
e5d94d2fe6a9fb379505b06842d74073db368e9bd2ea74cb730179aef250558b - Sigstore transparency entry: 2724535813
- Sigstore integration time:
-
Permalink:
dhananjaiyadav1234/Agent-lens@ec77f8896cfc4e159a2ae1646db9fdca9f8fee7f -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/dhananjaiyadav1234
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@ec77f8896cfc4e159a2ae1646db9fdca9f8fee7f -
Trigger Event:
release
-
Statement type: