Metadata-Version: 2.4
Name: agentbelt-core
Version: 0.1.1
Summary: Shared fingerprinting and storage primitives for the agentbelt suite.
Author: Saptarshi Bhattacharjee
License-Expression: MIT
Project-URL: Homepage, https://github.com/bsaptarshi/agentbelt
Keywords: llm,agents,observability
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Dynamic: license-file

# agentbelt-core

Shared fingerprinting and storage primitives for the `agentbelt` suite
(`agentbelt-budget`, `agentbelt-loop`, `agentbelt-sampler`), plus an
opt-in runtime profiler you can use directly. Most consumers pull this
in transitively as a dependency of the other packages, but the
profiling tool below is meant to be imported and used on its own.

```bash
pip install agentbelt-core   # usually pulled in transitively
```

## What's here

- `fingerprint.py` — normalize tool calls/results (volatile-field stripping,
  stable JSON), hash, and fuzzy token-overlap similarity
- `keyed_store.py` — thread-safe per-key state base class
- `profiling.py` — opt-in runtime profiler for measuring your own real
  usage of any agentbelt package (see below)

## Profiling your own usage

The numbers published in the suite's root README are measured on a
synthetic benchmark, on someone else's hardware. If you want to know the
actual overhead `agentbelt-budget`/`-loop`/`-sampler` add inside *your*
application, under *your* real traffic - wrap the calls you care about:

```python
from agentbelt_core.profiling import Profiler

profiler = Profiler()

with profiler.track("budget.check"):
    breaker.check(session_id)

with profiler.track("loop.record"):
    loop_detector.record(session_id, tool=tool_name, args=args, result=result)

# ... run your real workload for a while, then:
print(profiler.report_text())
# budget.check: n=4210  median=3.1us  mean=3.4us  min=1.9us  max=41.2us
# loop.record: n=4210  median=14.8us  mean=15.9us  min=9.1us  max=88.0us
```

Or as a decorator, if you'd rather instrument a function once instead of
wrapping every call site:

```python
@profiler.profiled()
def call_llm_with_guard(session_id, prompt):
    breaker.check(session_id)
    response = call_llm(prompt)
    breaker.record_response(session_id, response, model="gpt-4o")
    return response
```

**Memory profiling is opt-in and off by default** (`tracemalloc` has its
own overhead if left running continuously):
```python
profiler = Profiler(track_memory=True)          # every tracked block measures memory
# or, per call:
with profiler.track("budget.check", memory=True):
    breaker.check(session_id)
```

`profiler.report()` gives you the same data as a plain dict, if you'd
rather log it as structured data than print the text summary. `.reset()`
clears accumulated stats (for one label, or everything) if you want to
profile in rolling windows rather than accumulate for the process
lifetime.

This adds zero overhead when you don't use it — no background thread, no
daemon, nothing runs unless you explicitly wrap something in `.track()`
or `@profiled`.

## License

MIT
