---
title: "dops — Observability"
description: "Agent observability — tokens, costs, tools, and errors"
---

import { Steps, Tabs, TabItem, Aside } from '@astrojs/starlight/components';

<img src="/logos/logo-dops-complete.png" alt="dops" width="320" style="margin-bottom: 1.5rem;" />

dops is the observability layer for dtoolkit. It answers three questions about your AI coding agents: **what are they doing**, **how much does it cost**, and **where do they fail**. It ingests native transcript formats from Claude Code, Codex CLI, Gemini CLI, and OpenCode, then stores structured telemetry in SQLite for querying via REST API, MCP tools, CLI, or a web dashboard.

## Architecture

dops runs two Fastify servers from a single `dops start` command:

| Component | Default Port | Purpose |
|-----------|-------------|---------|
| REST + MCP API | 7883 | Ingest telemetry, query sessions, token usage, tool stats, errors |
| Dashboard | 7884 | Web UI for exploring sessions, costs, and tool analytics |

Data is stored in `~/.dops/dops.db` (SQLite with WAL mode). The database tracks five entity types: **sessions**, **events**, **tool calls**, **token usage**, and **errors**.

All API endpoints (except `/health`) require Bearer token authentication. The admin token is generated during `dops init`, and additional API keys can be created in the `api_keys` table.

## Quick start

<Steps>
1. **Install dops globally:**

   <Tabs>
     <TabItem label="npm">
       ```bash title="install"
       npm install -g @dtoolkit/dops
       ```
     </TabItem>
     <TabItem label="pnpm">
       ```bash title="install"
       pnpm add -g @dtoolkit/dops
       ```
     </TabItem>
   </Tabs>

2. **Initialize** — the interactive wizard creates your data directory, database, and auth token:

   ```bash title="terminal"
   dops init
   ```

   This creates `~/.dops/` with `config.json` and `dops.db`.

3. **Start the server:**

   ```bash title="terminal"
   dops start
   ```

   You should see:

   ```
   dops is running

     API:        http://0.0.0.0:7883
     MCP:        http://0.0.0.0:7883/mcp
     Dashboard:  http://localhost:7884
   ```

4. **Ingest your first transcripts:**

   ```bash title="terminal"
   dops ingest --url http://localhost:7883 --token <your-token>
   ```

   This scans the default transcript locations for Claude Code, Codex, Gemini, and OpenCode, then pushes session data to the server.
</Steps>

## Token tracking

Every token usage record captures four dimensions per model interaction:

| Field | Description |
|-------|-------------|
| `input_tokens` | Tokens sent to the model |
| `output_tokens` | Tokens generated by the model |
| `cache_read` | Tokens served from prompt cache (cheaper) |
| `cache_write` | Tokens written into prompt cache |

Token data is recorded per session and linked to the model that processed it. You can query totals across sessions, filter by source or model, and view time-series breakdowns at hourly or 15-minute intervals.

<Aside type="tip">
  Cache read tokens are significantly cheaper than input tokens for all major model families. Tracking the cache read ratio helps you understand how effectively your agent sessions leverage prompt caching.
</Aside>

## Cost estimation

dops ships with built-in pricing for major model families. Costs are calculated per 1M tokens:

| Model | Input | Output | Cache Read | Cache Write |
|-------|------:|-------:|-----------:|------------:|
| claude-opus-4-6 | $15.00 | $75.00 | $1.50 | $18.75 |
| claude-sonnet-4-6 | $3.00 | $15.00 | $0.30 | $3.75 |
| claude-haiku-4-5 | $0.80 | $4.00 | $0.08 | $1.00 |
| gpt-4o | $2.50 | $10.00 | $1.25 | $2.50 |
| gpt-4.1 | $2.00 | $8.00 | $0.50 | $2.00 |
| o3 | $2.00 | $8.00 | $0.50 | $2.00 |
| gemini-3-flash-preview | $0.15 | $0.60 | $0.04 | $0.15 |

Default pricing lives in `config.json` under the `pricing` key. You can override or extend it with custom models:

```json
{
  "pricing": {
    "my-custom-model": {
      "input": 1.0,
      "output": 5.0,
      "cache_read": 0.1,
      "cache_write": 1.0
    }
  }
}
```

The cost formula for each model is:

```
cost = (input_tokens / 1M) * input_price
     + (output_tokens / 1M) * output_price
     + (cache_read / 1M) * cache_read_price
     + (cache_write / 1M) * cache_write_price
```

Models without pricing data appear as "no pricing" in the CLI output. The `/health` endpoint exposes the active pricing table so clients can compute costs locally.

## Tool analytics

dops tracks every tool call made by your agents, recording:

| Field | Description |
|-------|-------------|
| `tool_name` | Name of the tool invoked (e.g., `Read`, `Bash`, `Edit`) |
| `success` | Whether the call succeeded |
| `duration_ms` | Execution time in milliseconds (when available) |
| `args` | Tool arguments (JSON, optional) |
| `error` | Error message on failure (optional) |

The `/stats/tools` endpoint and the `tools` MCP tool aggregate this into per-tool summaries: total calls, success count, failure count, and average duration. This lets you identify which tools fail most often, which take the longest, and which your agents rely on most heavily.

## Transcript ingestion

The `dops ingest` command reads native transcript formats from local CLI data directories. Each adapter (from `@dtoolkit/adapter-*`) implements a `TelemetryExtractor` that knows how to parse its CLI's transcript format and produce a normalized `ParsedSession` object.

### Supported sources

| Source | Transcript location | Adapter package |
|--------|-------------------|----------------|
| Claude Code | `~/.claude/` | `@dtoolkit/adapter-claude` |
| Codex CLI | `~/.codex/` | `@dtoolkit/adapter-codex` |
| Gemini CLI | `~/.gemini/` | `@dtoolkit/adapter-gemini` |
| OpenCode | `~/.opencode/` | `@dtoolkit/adapter-opencode` |

### How ingestion works

<Steps>
1. The CLI loads the **ingest state** from `~/.dops/ingest-state.json`, which tracks which sessions have already been pushed.
2. Each adapter's `scan()` method reads transcripts from the last N days and returns new sessions not in the state file.
3. For each session, the client pushes data to the dops server via REST: create the session, then push token usage records, tool calls, and finally end the session.
4. The state file is updated so the same sessions are not ingested again on the next run.
</Steps>

### Ingestion options

```bash title="terminal"
# Ingest last 30 days (default) from all sources
dops ingest --url http://localhost:7883 --token <token>

# Ingest last 7 days from Claude Code only
dops ingest --url http://localhost:7883 --token <token> --days 7 --source claude

# Dry run — scan and report without pushing data
dops ingest --url http://localhost:7883 --token <token> --dry-run

# Ingest from specific sources
dops ingest --url http://localhost:7883 --token <token> --source claude codex
```

<Aside type="caution">
  The `--url` and `--token` flags are required for `ingest`, `stats`, and `costs` commands. These are the URL of a running dops server and the admin token (or an API key with write permissions).
</Aside>

Sessions are tagged with `source@hostname` (e.g., `claude@macbook`) to distinguish data from different machines when multiple agents ingest into the same server.

### Automated ingestion with cron

Ingestion is not automatic — you need to run `dops ingest` periodically to pull new transcripts. The recommended approach is a cron job that runs every few hours:

```bash title="terminal"
# Edit your crontab
crontab -e
```

Add a line to ingest every 4 hours:

```bash title="crontab"
0 */4 * * * dops ingest --url http://localhost:7883 --token <your-token> >> /tmp/dops-ingest.log 2>&1
```

<Aside type="tip">
  For teams, each developer runs their own cron job pointing to a shared dops server. This way all agent telemetry flows into a single dashboard without manual effort.
</Aside>

## Dashboard

The web dashboard runs on port 7884 (one above the API port) and provides a visual interface for exploring your observability data. It is a single-page React application served as a static HTML file with CDN dependencies -- no build step required.

The dashboard connects to the REST API on port 7883 using your auth token. It supports light and dark themes and is responsive on mobile.

<video src="/screens/dops.webm" autoplay loop muted playsinline style="border-radius: 0.75rem; border: 1px solid oklch(0.92 0.01 248); margin-top: 1rem;" />

Open it in your browser after starting the server:

```
http://localhost:7884
```

## MCP tools

dops exposes six MCP tools on the same port as the REST API (`/mcp` endpoint). These let your AI agents query their own observability data during a session.

| Tool | Description | Key parameters |
|------|-------------|---------------|
| `observe` | Quick overview of agent activity: total sessions, tokens, tool calls, errors | `from`, `to`, `source` |
| `stats` | Time-series token usage data for graphing, bucketed by hour or 15 minutes | `from`, `to`, `source`, `interval` |
| `tools` | Tool usage analytics: call frequency, success/failure rates, average duration | `from`, `to`, `source` |
| `sessions` | List recent agent sessions with token and tool summaries | `source`, `model`, `from`, `to`, `limit` |
| `session_detail` | Full details for a single session: all token usage, tool calls, and errors | `id` |
| `errors` | List recent errors across all agent sessions | `from`, `to`, `limit` |

All date parameters accept ISO 8601 strings. The `source` filter accepts `claude`, `codex`, `gemini`, or `opencode`.

### Example: ask your agent about its own usage

```
You: How many tokens have I used with Claude this week?
```

The agent calls the `observe` MCP tool with a `from` date and `source: "claude"`, then summarizes the results.

## CLI commands

| Command | Description | Required flags |
|---------|-------------|---------------|
| `dops init [path]` | Initialize dops (config + database). Interactive wizard for port, host, and token. | None (interactive) |
| `dops start [path]` | Start the API server and dashboard | None |
| `dops status [path]` | Check if dops is running and show summary stats | None |
| `dops ingest` | Scan local CLI transcripts and push telemetry to server | `--url`, `--token` |
| `dops stats` | Show agent usage statistics (sessions, tokens, tools, errors) | `--url`, `--token` |
| `dops costs` | Show estimated costs broken down by model | `--url`, `--token` |

### init

```bash title="terminal"
dops init
```

The wizard prompts for:
- **Data path** (default: `~/.dops`)
- **API port** (default: `7883`)
- **Bind address** (`0.0.0.0` for network access, `127.0.0.1` for localhost only)
- **Access token** (auto-generated `sk-dop_*` format)

For CI and Docker environments, use `--non-interactive` mode. Configuration can also be set via environment variables:

| Variable | Purpose |
|----------|---------|
| `DOPS_PORT` | Override API port |
| `DOPS_HOST` | Override bind address |
| `DOPS_TOKEN` | Override access token |
| `DOPS_DATA` | Override data path (non-interactive mode) |
| `DOPS_NON_INTERACTIVE` | Set to `1` for non-interactive init |

### stats

```bash title="terminal"
dops stats --url http://localhost:7883 --token <token> --days 7
```

Output includes:
- **Overview**: total sessions, input/output/cache tokens, tool calls, errors
- **By source**: sessions and output tokens per CLI source
- **By model**: sessions and output tokens per model
- **Top tools**: call count, success rate, and average duration for the top 15 tools

### costs

```bash title="terminal"
dops costs --url http://localhost:7883 --token <token> --days 30
```

Output is a table of models with session count, output tokens, cache read tokens, and estimated cost. Models without pricing configuration show "no pricing" instead of a dollar amount.

## REST API

All endpoints (except `/health`) require a `Authorization: Bearer <token>` header.

### Query endpoints

| Method | Path | Description |
|--------|------|-------------|
| `GET` | `/health` | Server health, version, pricing table, and record counts |
| `GET` | `/sessions` | List sessions. Query params: `source`, `model`, `status`, `from`, `to`, `limit`, `offset` |
| `GET` | `/sessions/:id` | Full session detail with token usage, tool calls, and errors |
| `GET` | `/stats/timeseries` | Time-series token usage. Query params: `from`, `to`, `source`, `interval` (`1h` or `15m`) |
| `GET` | `/stats/tools` | Per-tool call frequency, success rate, average duration. Query params: `from`, `to`, `source` |
| `GET` | `/stats/models` | Per-model token usage aggregates. Query params: `from`, `to` |
| `GET` | `/stats/sources` | Per-source session and token aggregates. Query params: `from`, `to` |

### Ingest endpoints

| Method | Path | Description |
|--------|------|-------------|
| `POST` | `/sessions` | Create a new session. Body: `{ source, model?, started_at?, metadata? }` |
| `PATCH` | `/sessions/:id` | End a session. Body: `{ status, ended_at? }` |
| `POST` | `/events` | Insert a single event. Body: `{ session_id, type, timestamp?, data? }` |
| `POST` | `/events/batch` | Insert up to 1000 events in one request. Body: `{ events: [...] }` |
| `POST` | `/tool-calls` | Record a tool call. Body: `{ session_id, tool_name, success?, duration_ms?, args?, error? }` |
| `POST` | `/token-usage` | Record token usage. Body: `{ session_id, model, input_tokens, output_tokens, cache_read, cache_write }` |
| `POST` | `/errors` | Record an error. Body: `{ session_id?, type, message, stack? }` |

### MCP endpoint

| Method | Path | Description |
|--------|------|-------------|
| `POST` | `/mcp` | MCP Streamable HTTP transport (stateless, one server per request) |

<Aside type="tip">
  The MCP endpoint uses Streamable HTTP transport on the same port as REST. You can connect any MCP client to `http://localhost:7883/mcp` to access the six observability tools.
</Aside>

## Database schema

dops uses five core tables plus a key management table:

| Table | Purpose | Key columns |
|-------|---------|------------|
| `sessions` | Agent sessions with status tracking | `id`, `source`, `model`, `started_at`, `ended_at`, `status` |
| `events` | Generic timestamped events | `session_id`, `type`, `timestamp`, `data` (JSON) |
| `tool_calls` | Individual tool invocations | `session_id`, `tool_name`, `success`, `duration_ms`, `args`, `error` |
| `token_usage` | Token counts per model interaction | `session_id`, `model`, `input_tokens`, `output_tokens`, `cache_read`, `cache_write` |
| `errors` | Error records with type and stack | `session_id`, `type`, `message`, `stack` |
| `api_keys` | Bearer token authentication | `token`, `user_id`, `permissions`, `status` |

The database uses WAL journal mode for concurrent read access and foreign keys with CASCADE deletes on the session ID.

## Integration with dtoolkit

dops works standalone but composes naturally with the rest of the dtoolkit suite:

- **[dbrain](/docs/products/dbrain/)** stores persistent memory; dops tracks the operational cost of agent sessions that use it.
- **[dcontext](/docs/products/dcontext/)** injects context and saves transcripts; dops ingests those transcripts to extract telemetry.
- **[dwork](/docs/products/dwork/)** manages tasks; dops can show you how much each coding session cost.
- **[dproxy](/docs/products/dproxy/)** routes model calls; dops provides the data to evaluate which models deliver the best cost-to-quality ratio.

The adapter packages (`@dtoolkit/adapter-claude`, `@dtoolkit/adapter-codex`, `@dtoolkit/adapter-gemini`, `@dtoolkit/adapter-opencode`) are shared between dops and dproxy, ensuring consistent transcript parsing across the toolkit.
