# Inspect Calls And Metrics

> >

- **Type:** Skill
- **Install:** `agentstack add skill-patterai-skills-inspect-calls-and-metrics`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [PatterAI](https://agentstack.voostack.com/s/patterai)
- **Installs:** 0
- **Category:** [AI & ML](https://agentstack.voostack.com/c/ai-and-ml)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [PatterAI](https://github.com/PatterAI)
- **Source:** https://github.com/PatterAI/skills/tree/main/inspect-calls-and-metrics
- **Website:** https://www.skills.sh/patterai/skills

## Install

```sh
agentstack add skill-patterai-skills-inspect-calls-and-metrics
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Inspect calls and metrics with Patter

Patter persists every call to an in-memory `MetricsStore` (500-call ring
buffer by default), optionally backed by disk. The dashboard surfaces
live calls, transcripts, latency breakdowns, per-leg cost, and recordings.
You can also pull `CallMetrics` programmatically and export to CSV/JSON
for offline analysis.

## Mount the live dashboard

Patter ships a dashboard route you can mount on the same server as your
agent. Visit `http://localhost:8000/dashboard` to see live calls.

### Python

```python
import asyncio
from getpatter import Patter, Twilio, OpenAIRealtime2

async def main():
    phone = Patter(carrier=Twilio(), phone_number="+15550001234")
    agent = phone.agent(
        engine=OpenAIRealtime2(),
        system_prompt="...",
        first_message="Hi!",
    )
    # dashboard=True mounts /dashboard (UI) + /api/calls (REST) + /sse (live stream)
    await phone.serve(agent, tunnel=True, dashboard=True)

asyncio.run(main())
```

### TypeScript

```typescript
import { Patter, Twilio, OpenAIRealtime2 } from "getpatter";

const phone = new Patter({ carrier: new Twilio(), phoneNumber: "+15550001234" });
const agent = phone.agent({
  engine: new OpenAIRealtime2(),
  systemPrompt: "...",
  firstMessage: "Hi!",
});

await phone.serve({ agent, tunnel: true, dashboard: true });
```

Open `http://localhost:8000/dashboard`. Live calls appear at the top, with
real-time transcript, current cost, and latency p50/p90/p95/p99.

## Read metrics in code

`CallMetrics` is the canonical model — every finished call produces one.
The hook is **`on_call_end`** passed as a kwarg to `phone.serve(...)`. It
receives a `dict` (the `CallMetrics` serialized form), and is async.

### Python

```python
import asyncio
from getpatter import Patter, Twilio, OpenAIRealtime2

phone = Patter(carrier=Twilio(), phone_number="+15550001234")

async def on_end(metrics: dict) -> None:
    print(f"Call {metrics['call_id']} | {metrics['duration_seconds']:.1f}s")
    cost = metrics.get("cost", {})
    print(f"  Cost ${cost.get('total_usd', 0):.4f}: "
          f"STT ${cost.get('stt_usd', 0):.4f} · "
          f"LLM ${cost.get('llm_usd', 0):.4f} · "
          f"TTS ${cost.get('tts_usd', 0):.4f} · "
          f"Realtime ${cost.get('realtime_usd', 0):.4f} · "
          f"Telephony ${cost.get('telephony_usd', 0):.4f}")
    print(f"  Latency p99: {metrics.get('latency_p99', 0):.0f} ms")

agent = phone.agent(engine=OpenAIRealtime2(), system_prompt="...", first_message="Hi!")
asyncio.run(phone.serve(agent, tunnel=True, on_call_end=on_end))
```

### TypeScript

```typescript
import { Patter, Twilio, OpenAIRealtime2 } from "getpatter";

const phone = new Patter({ carrier: new Twilio(), phoneNumber: "+15550001234" });

const agent = phone.agent({
  engine: new OpenAIRealtime2(),
  systemPrompt: "...",
  firstMessage: "Hi!",
});

await phone.serve({
  agent,
  tunnel: true,
  onCallEnd: async (metrics) => {
    console.log(`Call ${metrics.call_id} | ${metrics.duration_seconds.toFixed(1)}s`);
    console.log(`  Cost $${metrics.cost?.total_usd?.toFixed(4) ?? "0"}`);
    console.log(`  Latency p99: ${metrics.latency_p99?.toFixed(0) ?? "0"} ms`);
  },
});
```

You can also pull live data programmatically from the `MetricsStore`
attached to the server — `phone.metrics_store` (Python) /
`phone.metricsStore` (TypeScript). It returns `None`/`null` until
`serve()` has bound; once the server is live, you can iterate calls.

## Export call history

`calls_to_csv` and `calls_to_json` operate on the `MetricsStore` exposed on
the running server.

### Python

```python
from getpatter import calls_to_csv, calls_to_json

store = phone.metrics_store          # available after serve() has bound
if store is not None:
    with open("calls.csv", "w") as f:
        f.write(calls_to_csv(store))
    import json
    with open("calls.json", "w") as f:
        json.dump(calls_to_json(store), f, indent=2)
```

### TypeScript

```typescript
import { callsToCsv, callsToJson } from "getpatter";
import { writeFileSync } from "fs";

const store = phone.metricsStore;     // available after serve() has bound
if (store) {
  writeFileSync("calls.csv", callsToCsv(store));
  writeFileSync("calls.json", JSON.stringify(callsToJson(store), null, 2));
}
```

## REST API (when dashboard is mounted)

| Route | Purpose |
|---|---|
| `GET /api/calls` | List recent calls (newest first). |
| `GET /api/calls/{call_id}` | Detailed metrics + transcript for one call. |
| `GET /sse` | Server-Sent Events stream of live call updates. |

Example with curl:

```bash
curl http://localhost:8000/api/calls | jq '.[] | {id: .call_id, cost: .cost.total_usd, duration: .duration_seconds}'
```

## Persist to disk (survive restarts)

By default the ring buffer is in-memory. Pass `persist=True` (or a path) to
mirror to disk:

### Python

```python
phone = Patter(
    carrier=Twilio(),
    phone_number="+15550001234",
    persist=True,                  # writes to ./.patter/calls.db
    # persist="/var/patter/calls.db",  # or a custom path
)
```

### TypeScript

```typescript
const phone = new Patter({
  carrier: new Twilio(),
  phoneNumber: "+15550001234",
  persist: true,                   // writes to ./.patter/calls.db
});
```

## Lock the dashboard down (bearer token)

When exposing the dashboard beyond `127.0.0.1`, gate it with a bearer token.
Patter ships token auth out of the box via the `dashboard_token` kwarg —
all `/dashboard`, `/api/calls`, and `/sse` routes then require
`Authorization: Bearer `.

### Python

```python
await phone.serve(agent, tunnel=True, dashboard_token="hunter2-rotate-me")
```

### TypeScript

```typescript
await phone.serve({ agent, tunnel: true, dashboardToken: "hunter2-rotate-me" });
```

For programmatic FastAPI/Express embedding (not just `phone.serve`), the
lower-level `make_auth_dependency(...)` (Python) / `makeAuthMiddleware(...)`
(TypeScript) factories are also exported — useful when you mount the
dashboard onto your own app and need a custom auth scheme.

Without auth, **do not bind to `0.0.0.0`** — call transcripts are PII.

## What's inside `CallMetrics`

Key fields (see `models.py` / `metrics.ts` for the full list):

| Field | Meaning |
|---|---|
| `call_id` | UUID, persistent across the call. |
| `duration_seconds` | Total call wall time. |
| `turns` | List of `TurnMetrics` (user/agent each). |
| `cost` | `CostBreakdown(stt_usd, llm_usd, tts_usd, realtime_usd, telephony_usd, total_usd)`. |
| `latency_avg / p50 / p90 / p95 / p99` | Turn-time percentiles in ms. |
| `provider_mode` | `"openai_realtime"`, `"elevenlabs_convai"`, `"pipeline"`. |
| `stt_provider / llm_provider / tts_provider / telephony_provider` | Provider identifiers. |
| `stt_model / llm_model / tts_model` | Model strings. |
| `transcript` | List of `(role, text)` tuples — set when `input_audio_transcription_model` is configured. |
| `context_tokens` | (0.7.0, pipeline mode) Peak estimated prompt size across the call, in the `chars / 4` token unit — chart it to spot context growth on long calls. |

Recording URLs live in the carrier's payload, surfaced in the
`call.recording.saved` log line — they are not exposed as a typed
`CallMetrics` field since 0.6.3. Enable recording with `phone.serve(..., recording=True)`.

## Gotchas

- **Ring buffer is 500 calls by default**. Older calls are evicted from
  memory (still on disk if `persist=True`). For long-running production,
  use the on-disk SQLite to query history.
- **Dashboard exposes transcripts (PII)** — always gate with auth + HTTPS
  in production.
- **`on_call_end` runs synchronously in the event loop**. Don't block —
  push to a queue for slow exporters.
- **Latency p99 == infinity for short calls** with  · 
- Metrics reference:  · 
- Call logging: 
- Pricing module:

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [PatterAI](https://github.com/PatterAI)
- **Source:** [PatterAI/skills](https://github.com/PatterAI/skills)
- **License:** MIT
- **Homepage:** https://www.skills.sh/patterai/skills

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** yes
- **Filesystem access:** yes
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-patterai-skills-inspect-calls-and-metrics
- Seller: https://agentstack.voostack.com/s/patterai
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
