# strands-token-telemetry

> Emit Strands Agents token usage as CloudWatch Embedded Metric Format (EMF) metrics.

## Install

pip install strands-token-telemetry

strands-agents is a peer dependency (>= 0.1.0).

## Public API

The package exports three names from strands_token_telemetry:

- TokenUsageHook — the main hook class (HookProvider)
- build_emf_payload — pure function to build an EMF dict
- default_emitter — prints compact JSON to stdout

## TokenUsageHook

A Strands HookProvider. Pass it in the hooks list when creating an Agent.

Constructor (all parameters are keyword-only):

  namespace: str = "Strands/AgentTokenUsage"
      CloudWatch metrics namespace.

  dimensions: list[list[str]] = [["Model"]]
      Dimension key sets for CloudWatch metrics.

  dimension_values: dict[str, str] = {}
      Static dimension key/value pairs. Keys must appear in dimensions.

  dimension_resolver: Callable[[AfterInvocationEvent], dict[str, str]] | None = None
      Called on each invocation with the event. Returns dimension key/value
      pairs that are merged with dimension_values (resolver wins on conflict).
      Use this when a value isn't known until runtime (e.g. model name from
      the provider response).

  extra_properties: dict[str, Any] | None = None
      Additional top-level properties in the EMF payload. These are NOT
      published as CloudWatch Metric dimensions but ARE queryable in
      CloudWatch Logs Insights. Use for user IDs, session IDs, trace IDs, etc.

  emitter: Callable[[dict[str, Any]], None] = default_emitter
      Function that receives the final payload dict. Override to route output
      to logging, a webhook, or /dev/null.

## Minimal usage

from strands import Agent
from strands_token_telemetry import TokenUsageHook

agent = Agent(hooks=[TokenUsageHook()])

## Common patterns

### Custom namespace with session context

hook = TokenUsageHook(
    namespace="AcmeInc/StrandsTokens",
    dimension_values={"Model": model_id},
    extra_properties={"UserId": user_id, "SessionId": session_id},
)
agent = Agent(hooks=[hook])

### Dynamic dimensions resolved at runtime

def resolve_dims(event):
    model = getattr(event.result, "model", "unknown") if event.result else "unknown"
    return {"Model": model}

agent = Agent(hooks=[
    TokenUsageHook(
        dimensions=[["Model", "Environment"]],
        dimension_values={"Environment": "prod"},
        dimension_resolver=resolve_dims,
    )
])

### Suppress stdout output (local development)

hook = TokenUsageHook(emitter=lambda payload: None)

### Pretty-print for debugging

import json
hook = TokenUsageHook(emitter=lambda p: print(json.dumps(p, indent=2)))

### Route to Python logging

import json, logging
log = logging.getLogger("token_telemetry")
hook = TokenUsageHook(emitter=lambda p: log.debug("%s", json.dumps(p)))

### Forward to an external service

import json, urllib.request

def webhook_emitter(payload):
    req = urllib.request.Request(
        "https://metrics.example.com/ingest",
        data=json.dumps(payload).encode(),
        headers={"Content-Type": "application/json"},
    )
    urllib.request.urlopen(req)

hook = TokenUsageHook(emitter=webhook_emitter)

## build_emf_payload

Pure function — useful if you want to build the EMF dict without the hook.

build_emf_payload(
    usage: dict[str, int],        # token counters (each becomes a metric)
    namespace: str,
    dimensions: list[list[str]],
    dimension_values: dict[str, str],
    extra_properties: dict[str, Any] | None = None,
    timestamp_ms: int | None = None,  # defaults to now
) -> dict[str, Any]

## Metrics emitted

The hook emits every key present in the SDK's usage dict as a Count metric.
Typical keys: inputTokens, outputTokens, totalTokens, cacheReadInputTokens,
cacheWriteInputTokens.

## Key concepts

- dimension_values vs extra_properties: dimension_values become CloudWatch
  Metric dimensions (you can alarm/filter on them). extra_properties are
  top-level JSON fields queryable in CloudWatch Logs Insights but not
  published as metric dimensions.

- dimension_resolver vs dimension_values: use dimension_values for values
  known at hook construction time (environment, service name). Use
  dimension_resolver for values only available at invocation time (model
  name from the response, agent name).

- emitter: the default prints compact JSON to stdout for the CloudWatch
  agent. Override it to send elsewhere or suppress output.

## Source

https://github.com/flockcover/strands-token-telemetry
