dops — Observability
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
Section titled “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
Section titled “Quick start”-
Install dops globally:
install npm install -g @dtoolkit/dopsinstall pnpm add -g @dtoolkit/dops -
Initialize — the interactive wizard creates your data directory, database, and auth token:
terminal dops initThis creates
~/.dops/withconfig.jsonanddops.db. -
Start the server:
terminal dops startYou should see:
dops is runningAPI: http://0.0.0.0:7883MCP: http://0.0.0.0:7883/mcpDashboard: http://localhost:7884 -
Ingest your first transcripts:
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.
Token tracking
Section titled “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.
Cost estimation
Section titled “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:
{ "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_priceModels 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
Section titled “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
Section titled “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
Section titled “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
Section titled “How ingestion works”- The CLI loads the ingest state from
~/.dops/ingest-state.json, which tracks which sessions have already been pushed. - Each adapter’s
scan()method reads transcripts from the last N days and returns new sessions not in the state file. - 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.
- The state file is updated so the same sessions are not ingested again on the next run.
Ingestion options
Section titled “Ingestion options”# Ingest last 30 days (default) from all sourcesdops ingest --url http://localhost:7883 --token <token>
# Ingest last 7 days from Claude Code onlydops ingest --url http://localhost:7883 --token <token> --days 7 --source claude
# Dry run — scan and report without pushing datadops ingest --url http://localhost:7883 --token <token> --dry-run
# Ingest from specific sourcesdops ingest --url http://localhost:7883 --token <token> --source claude codexSessions 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
Section titled “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:
# Edit your crontabcrontab -eAdd a line to ingest every 4 hours:
0 */4 * * * dops ingest --url http://localhost:7883 --token <your-token> >> /tmp/dops-ingest.log 2>&1Dashboard
Section titled “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.
Open it in your browser after starting the server:
http://localhost:7884MCP tools
Section titled “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
Section titled “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
Section titled “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 |
dops initThe wizard prompts for:
- Data path (default:
~/.dops) - API port (default:
7883) - Bind address (
0.0.0.0for network access,127.0.0.1for 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 |
dops stats --url http://localhost:7883 --token <token> --days 7Output 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
dops costs --url http://localhost:7883 --token <token> --days 30Output 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
Section titled “REST API”All endpoints (except /health) require a Authorization: Bearer <token> header.
Query endpoints
Section titled “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
Section titled “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
Section titled “MCP endpoint”| Method | Path | Description |
|---|---|---|
POST | /mcp | MCP Streamable HTTP transport (stateless, one server per request) |
Database schema
Section titled “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
Section titled “Integration with dtoolkit”dops works standalone but composes naturally with the rest of the dtoolkit suite:
- dbrain stores persistent memory; dops tracks the operational cost of agent sessions that use it.
- dcontext injects context and saves transcripts; dops ingests those transcripts to extract telemetry.
- dwork manages tasks; dops can show you how much each coding session cost.
- 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.