Skip to content
dtoolkit

dops — Observability

dops

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.

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

ComponentDefault PortPurpose
REST + MCP API7883Ingest telemetry, query sessions, token usage, tool stats, errors
Dashboard7884Web 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.

  1. Install dops globally:

    install
    npm install -g @dtoolkit/dops
  2. Initialize — the interactive wizard creates your data directory, database, and auth token:

    terminal
    dops init

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

  3. Start the server:

    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:

    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.

Every token usage record captures four dimensions per model interaction:

FieldDescription
input_tokensTokens sent to the model
output_tokensTokens generated by the model
cache_readTokens served from prompt cache (cheaper)
cache_writeTokens 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.

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

ModelInputOutputCache ReadCache 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_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.

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

FieldDescription
tool_nameName of the tool invoked (e.g., Read, Bash, Edit)
successWhether the call succeeded
duration_msExecution time in milliseconds (when available)
argsTool arguments (JSON, optional)
errorError 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.

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.

SourceTranscript locationAdapter package
Claude Code~/.claude/@dtoolkit/adapter-claude
Codex CLI~/.codex/@dtoolkit/adapter-codex
Gemini CLI~/.gemini/@dtoolkit/adapter-gemini
OpenCode~/.opencode/@dtoolkit/adapter-opencode
  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.
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

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

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:

terminal
# Edit your crontab
crontab -e

Add a line to ingest every 4 hours:

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

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:7884

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.

ToolDescriptionKey parameters
observeQuick overview of agent activity: total sessions, tokens, tool calls, errorsfrom, to, source
statsTime-series token usage data for graphing, bucketed by hour or 15 minutesfrom, to, source, interval
toolsTool usage analytics: call frequency, success/failure rates, average durationfrom, to, source
sessionsList recent agent sessions with token and tool summariessource, model, from, to, limit
session_detailFull details for a single session: all token usage, tool calls, and errorsid
errorsList recent errors across all agent sessionsfrom, 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.

CommandDescriptionRequired 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 dashboardNone
dops status [path]Check if dops is running and show summary statsNone
dops ingestScan local CLI transcripts and push telemetry to server--url, --token
dops statsShow agent usage statistics (sessions, tokens, tools, errors)--url, --token
dops costsShow estimated costs broken down by model--url, --token
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:

VariablePurpose
DOPS_PORTOverride API port
DOPS_HOSTOverride bind address
DOPS_TOKENOverride access token
DOPS_DATAOverride data path (non-interactive mode)
DOPS_NON_INTERACTIVESet to 1 for non-interactive init
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
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.

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

MethodPathDescription
GET/healthServer health, version, pricing table, and record counts
GET/sessionsList sessions. Query params: source, model, status, from, to, limit, offset
GET/sessions/:idFull session detail with token usage, tool calls, and errors
GET/stats/timeseriesTime-series token usage. Query params: from, to, source, interval (1h or 15m)
GET/stats/toolsPer-tool call frequency, success rate, average duration. Query params: from, to, source
GET/stats/modelsPer-model token usage aggregates. Query params: from, to
GET/stats/sourcesPer-source session and token aggregates. Query params: from, to
MethodPathDescription
POST/sessionsCreate a new session. Body: { source, model?, started_at?, metadata? }
PATCH/sessions/:idEnd a session. Body: { status, ended_at? }
POST/eventsInsert a single event. Body: { session_id, type, timestamp?, data? }
POST/events/batchInsert up to 1000 events in one request. Body: { events: [...] }
POST/tool-callsRecord a tool call. Body: { session_id, tool_name, success?, duration_ms?, args?, error? }
POST/token-usageRecord token usage. Body: { session_id, model, input_tokens, output_tokens, cache_read, cache_write }
POST/errorsRecord an error. Body: { session_id?, type, message, stack? }
MethodPathDescription
POST/mcpMCP Streamable HTTP transport (stateless, one server per request)

dops uses five core tables plus a key management table:

TablePurposeKey columns
sessionsAgent sessions with status trackingid, source, model, started_at, ended_at, status
eventsGeneric timestamped eventssession_id, type, timestamp, data (JSON)
tool_callsIndividual tool invocationssession_id, tool_name, success, duration_ms, args, error
token_usageToken counts per model interactionsession_id, model, input_tokens, output_tokens, cache_read, cache_write
errorsError records with type and stacksession_id, type, message, stack
api_keysBearer token authenticationtoken, user_id, permissions, status

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

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.