Usage
Privacy-safe call log for tracking spend.
Marmot keeps a privacy-safe log of every metered call so you can answer "what did I run this week and how much did it cost?" without enabling any per-provider response cache or binding sessions. The log records call shape and outcome but never prompt text, query strings, or person identifiers.
marmot usage # last 7 days, grouped by provider
marmot usage --since 24h # last 24h
marmot usage --by verb # grouped by verb
marmot usage --provider parallel
marmot usage --failed-only
marmot usage --json
marmot usage prune --older-than 90dWhat's recorded
One JSON record per call, written to ~/.marmot/usage/<UTC-DATE>.jsonl. Schema:
| Field | Type | Notes |
|---|---|---|
request_id | string (UUID) | Unique per request. For async work, equals the provider's task id so submit/poll/completion records can be joined. (Pre-0.6.0 records used call_id; the reader aliases the legacy field automatically.) |
ts | ISO 8601 | Wall-clock when the call returned. |
verb | string | search, scrape, run, enrich, etc. |
provider | string | Provider slug. |
model | string | undefined | Present for AI verbs. |
preset | string | undefined | Preset NAME (never contents). |
flags | object | undefined | Non-sensitive flag values: limit, depth, freshness, format, type, etc. |
flag_presence | object | undefined | Sensitive flags as boolean presence only: includeDomains, email, linkedin, schema, etc. |
cached | boolean | True when served from local response cache. |
duration_ms | integer | Wall-clock duration. |
quantity | object | undefined | Verb-shaped counts. AI: tokens_input, tokens_output, tokens_cache_read, tokens_cache_write. Web: results, pages, urls, entities, citations. |
cost | number | null | undefined | USD cost when the provider reports it (OpenRouter, AI Gateway). null otherwise. |
exit | 'ok' | 'error' | Call outcome. |
error_category | string | undefined | Present on error: validation, provider, auth, cache, io. |
session | string | null | Session name when bound. |
sensitive | object | undefined | Opt-in audit payload. Populated only when logging.recordSensitive: true is configured (or MARMOT_RECORD_SENSITIVE=1 is set). Contains the actual prompt, query, target URLs, and identifier values that flag_presence flags as present. NEVER recorded by default. See Sensitive recording below. |
What's NOT recorded by default
Never written unless you explicitly opt in: prompt text, query text, system prompt, schema bodies, --include-domains and --exclude-domains values, all enrich/lookup/verify identifiers (--email, --linkedin, --phone, --first-name, etc.), API keys.
Reading the log
marmot usage # default 7 days, by provider
marmot usage --since 1h # last hour
marmot usage --since 24h # last 24h
marmot usage --since 30d # last 30 days
marmot usage --from 2026-05-01 --to 2026-05-06 # custom range
marmot usage --by verb # group by verb
marmot usage --by day # per-day breakdown
marmot usage --by model # per-model (AI verbs)
marmot usage --provider parallel # filter to one
marmot usage --verb search # filter to one
marmot usage --failed-only # only error rows
marmot usage --json # structured envelope--since accepts a positive integer plus h, d, or w: 1h, 24h, 7d, 4w. Composable: marmot usage --since 30d --by verb --provider openrouter --json returns 30 days of OpenRouter calls grouped by verb as JSON.
What you get
Every grouping (by provider / verb / day / model) reports:
requests,errors,errorRate,cached,cacheHitRate(top-leveltotalsonly — rows omiterrorRate/cacheHitRate)durationTotalMs,durationAvgMs,durationP50Ms,durationP95MscostTotal,costAvgUsd(overrequestsWithCost),requestsWithoutCostquantityTotals— sum of every numeric child key
The human-formatted output renders all timestamps in your local timezone. Storage stays UTC (one file per UTC day) so file boundaries are stable. The window header adapts to the range:
- Sub-day windows show time-of-day on both sides —
Usage — last 1h (May 6 09:14 to 10:14). - Multi-day windows with
--sinceecho the duration token —Usage — last 7d (May 1 to May 8). - Explicit
--from/--toshow the range only —Usage — May 1 to May 8.
Cost is only summed where the provider reports it. The summary always discloses how many calls had cost data so you know your coverage.
Live tail — --watch
marmot usage --watch prints each new record as it lands in today's usage file. Useful while running a batch job in another shell or iterating on a script:
marmot usage --watch # human format, one record per line
marmot usage --watch --json # JSONL on stdout for piping
marmot usage --watch --provider openrouter # filter live
marmot usage --watch --verb run --failed-only # only failed run callsImplementation notes:
- Polls today's
~/.marmot/usage/<UTC-DATE>.jsonlevery 500ms. Initial scan jumps to end-of-file so existing records aren't replayed — you only see what's new. - Filters (
--provider,--verb,--failed-only) apply per record. - At UTC midnight the watcher swaps to the new day's file automatically; a one-line stderr note marks the rollover.
- Ctrl-C exits cleanly.
Browse individual calls — marmot history
marmot history lists individual recent records (newest first) instead of aggregates. Useful when you need the timestamp / duration / outcome of a specific call without the rollup math:
marmot history # default last 10
marmot history --since 1h --limit 50 # last hour, up to 50
marmot history --provider parallel --json # filter + structured envelope
marmot history --failed-only # only error rows| Flag | Description |
|---|---|
--since <duration> | Time window: Nh, Nd, Nw (default 7d). |
--from <YYYY-MM-DD> | Window lower bound (overrides --since). |
--to <YYYY-MM-DD> | Window upper bound (inclusive). |
--provider <slug> | Filter to one provider. |
--verb <name> | Filter to one verb. |
--failed-only | Only error records. |
--limit <n> | Max records to return (default 10, cap 1000). |
--json | Emit { ok, window, limit, count, records: [...] }. |
Timestamps render in your local timezone. The preset_id field on each record is resolved to the current slug at render time, so renames don't break historical readability — if the preset has been deleted, the row falls back to (preset:<short-id>).
Pruning
marmot usage prune --older-than 90dDeletes day files older than the cutoff. Returns the number of files deleted and bytes freed.
Disabling
Default: ON. Three precedence levels (highest first):
- Per call:
marmot search "..." --no-logskips the record for this invocation only. - Env var:
MARMOT_NO_LOG=1 marmot search "..."does the same — useful for scripts. - Globally:
marmot config set logging.enabled false.
marmot doctor reports the current state.
Sensitive recording (opt-in)
The default log captures call shape and outcome but never the actual content (prompt, query, URLs, identifiers). For a full audit trail — the kind you'd want when reconstructing exactly what was asked of which provider — opt in:
marmot config set logging.recordSensitive trueOr per-call: MARMOT_RECORD_SENSITIVE=1 marmot search "...".
When opted in, every record gains a sensitive field with verb-appropriate contents:
| Verb | Sensitive payload |
|---|---|
run | { prompt, system?, schema? } |
image / speak / video | { prompt, flags?: { negative | instructions } } |
transcribe | { urls: [audioPath], flags?: { prompt } } |
search / answer | { query, flags?: { includeDomains, excludeDomains, afterDate, beforeDate } } |
scrape / map | { urls, flags?: { query | search } } |
crawl | { urls: [url], flags?: { instructions, includePaths, excludePaths } } |
research / findall | { query, schema?, flags?: { instructions | matchConditions } } |
enrich / lookup / verify | { flags?: { email, linkedin, phone, name, firstName, lastName, q, title, ... } } |
Per-call override: --redact
Even with recordSensitive on globally, you can redact a single call:
marmot search "this query is private" --redactThe record is still written (with metadata + flag_presence), but the sensitive field is omitted. Same effect: MARMOT_REDACT=1 marmot search "...".
Precedence (highest first):
--redactflag orMARMOT_REDACT=1— force OFF.MARMOT_RECORD_SENSITIVE=1— force ON for this call.config.logging.recordSensitive— global default (false unless set).
Verb coverage
As of 0.5.0, usage logging is wired into all 15 verbs: AI (run, image, speak, transcribe, video), web (search, scrape, answer, map, crawl, research, findall), and data (enrich, lookup, verify). Async verbs (crawl, research, findall) use the provider's task id as request_id so submit / poll / completion records can be joined.
File format on disk
~/.marmot/usage/2026-05-06.jsonl
~/.marmot/usage/2026-05-07.jsonl
~/.marmot/usage/2026-05-08.jsonlOne UTC day per file, append-only. JSONL — one record per line. Files are mode 0600, directory 0700.