keelwave

Introduction

Sentry for AI agents — open-source and self-hosted. See exactly why an agent looped, stalled, or burned tokens.

Sentry for AI agents — open-source and self-hosted. See exactly why an agent looped, stalled, or burned tokens, not just that latency looked fine.

import os
from keelwave import Keelwave

client = Keelwave(
    api_key=os.environ["KEELWAVE_API_KEY"],
    endpoint=os.environ.get("KEELWAVE_ENDPOINT", "http://localhost:8080"),
)


@client.observe(name="web_search", step_type="tool_call")
def web_search(q: str) -> dict:
    # Replace with a real search implementation.
    return {"results": [f"result for: {q}"]}


@client.agent(name="research-agent")
def run_agent(question: str) -> str:
    results = web_search(q=question)
    return f"Found: {results['results'][0]}"

That is the whole integration. @client.agent opens and closes a run; every @client.observe call inside it becomes a step on that run, fingerprinted for loop detection.

The problem

Infrastructure monitoring answers "is the service up?" — latency, error rate, throughput. For an AI agent, all three can look perfect while the run is a failure:

  • It gets stuck in a loop, calling the same tool with the same input over and over.
  • It burns tokens wandering toward an answer it never reaches.
  • It terminates for the wrong reason — context limit, max steps — instead of finishing cleanly.
  • It produces wrong output that no HTTP status code will ever flag.

Latency is fine. Error rate is 0%. Tools that measure infrastructure, or that dump raw traces, don't tell you why the agent went wrong or where in its reasoning it broke.

What keelwave records

Agent runs

One record per agent invocation: status, termination reason, total steps, tokens, cost, and duration.

Decision steps

Every step in the loop in order — think, tool_call, and any custom step type you emit.

Loop detection

A SHA-256 fingerprint of tool_name + input on each step. A repeat within one run flags the run as looping.

Token and cost tracking

Tokens and cost roll up per step and per run, so you can see reasoning degrade before the run fails.

LLM traces

Model, provider, tokens, latency, and status for every underlying model call, linked back to its run.

Dashboard

Run list with completion and loop rate, per-run step timeline, tool stats, and agent health.

Loop detection is the piece to understand first. Each tool call is hashed together with its input, and the first repeat inside a run marks the run as looping and records the step index where the repetition started — so you can point at the exact step that began the loop.

The @observe decorator does this for you. Called by hand, the TypeScript run.toolCall() fingerprints internally, while Python's run.tool_call() only sends the step — pair it with run.check_fingerprint(). See loop detection.

How the pieces fit

Your agent                keelwave API              TimescaleDB           Dashboard
Python / TS SDK  ──────▶  Go binary, :8080  ──────▶  Postgres      ──────▶  React SPA
                 POST                        COPY   + hypertables   query   (same origin)
                 /v1/ingest/*                                                :8080
  1. SDK@agent / @observe (or client.run(...)) emit runs, steps, and LLM traces. Adapters wrap your provider client so model calls are recorded without extra code.
  2. Ingest API — the Go server exposes /v1/ingest/ai, /v1/ingest/agent/runs, /v1/ingest/agent/runs/{id}/finish, and /v1/ingest/agent/steps, all authenticated with a kw_... API key. High-volume inserts go through a batch buffer into COPY.
  3. TimescaleDB — time-series tables (agent_runs, agent_steps, ai_traces) stored as hypertables, with views for tool stats, run health, and loops.
  4. Dashboard — a React SPA embedded in the same binary and served from the same origin, so the API and the UI both live at http://localhost:8080.

One Go binary, one database. docker compose up and go.

keelwave is self-host first. There is no hosted cloud offering yet — you run the server, and your agent data stays in your own database.

Start here

On this page