AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Inspect Calls And Metrics

skill-patterai-skills-inspect-calls-and-metrics · by PatterAI

>

No reviews yet
0 installs
19 views
0.0% view→install

Install

$ agentstack add skill-patterai-skills-inspect-calls-and-metrics

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access Used
  • Filesystem access Used
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-patterai-skills-inspect-calls-and-metrics)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
2mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Inspect Calls And Metrics? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

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

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

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

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

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

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

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:

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

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

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

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

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.

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

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.