Cost Tracking

The Tabstack API exposes no /v1/usage endpoint and no per-call token cost header. The only place credits are visible is the console dashboard — an authenticated web UI showing “CREDITS AVAILABLE” as a number.

This creates a problem for agent pipelines: how does an agent know whether it has budget for the next call?

The design

tabstack usage implements a local accounting system:

Every API call ──append──► ~/.config/tabstack/usage.jsonl
                              │
Console dashboard ──calibrate─┤
  (manual or scraped)         │
                              ▼
                    Learned per-verb costs
                              │
                              ▼
                    Estimated balance = last_reading − priced_calls_since

Three operations, each independent:

Record: every successful API call appends a ledger entry with the verb, timestamp, and latency. This is best-effort — a failed append never breaks the call.

Calibrate: feed in a real balance reading. Either manually (tabstack usage set 87500) or by scraping the dashboard with a session cookie (tabstack usage sync). When a new reading arrives, the system distributes the consumed delta across the logged calls in the window, weighted by the known relative cost of each verb. The result updates the learned per-verb averages.

Estimate: any time, tabstack usage computes last_balance − sum(learned_cost × call_count_since) and shows the estimated remaining credits.

The cookie scrape

The dashboard shows the balance in HTML. The CLI can fetch that HTML with the user’s session cookie:

# Save your console session cookie once
tabstack usage cookie 'session_id=eyJ...'

# Sync the balance from the dashboard
tabstack usage sync
# ✓ synced: 4,750 credits (from /dashboard)

The cookie must be in name=value format — copy the full cookie string from browser DevTools (Network → request headers → Cookie), not just the value.

The scraper:

  1. Tries /dashboard, then /, /usage, /billing in order
  2. Strips all HTML tags to plain text
  3. Matches patterns like “5,750 CREDITS AVAILABLE” and “credits remaining”
  4. Distinguishes an expired cookie (login page returned) from a page that genuinely has no balance

The cookie is stored at ~/.config/tabstack/usage.json with Unix permissions 0600 — the same file that holds learned costs, because it’s sensitive.

Learning per-verb costs

The default cost weights (extract=1, generate=3, research=25, automate=40) are priors. After two calibration readings, the system learns actual costs:

If the balance drops from 1000 to 900 and you ran 2 extract calls in between, the system infers each extract costs ~50 credits and updates the learned average. Subsequent estimates use the learned value, not the prior.

The math uses a sample-weighted blend: later calibrations refine the estimate rather than replacing it:

new_avg = (old_avg × old_samples + observed_cost × new_samples) / total_samples

The measured rule

Two controlled experiments ran during development to verify the cost/quality tradeoff between extract and research:

Experiment 1 (known sources): Extract five pages, synthesize in context. Research the same question. Extract won on quality at ~42× lower credit cost.

Experiment 2 (cross-source synthesis, designed to favor research): Research a question requiring synthesis across unfamiliar sources. Research still missed specific numbers, sentiment data, and thematic conclusions that extract + in-context synthesis caught.

Verdict: 1 research ≈ 25 extracts. Use extract when you can name the URLs. Use research when source discovery is the actual hard part. Never use research as a fact pipe — extract the pages it cites to verify specific numbers.

This rule is encoded into the CLI’s agent skill so agents learn it at install time.

Checking before a pipeline

Before running a many-call pipeline:

tabstack usage --json
# {"estimated": 4750, "learned": {"extract": {"avg": 12, "samples": 8}, "research": {"avg": 248}}}

tabstack usage
# ● Estimated credits remaining: ~4,750
#   extract   ~12 cr/call  (8 samples)
#   research  ~248 cr/call (2 samples)
#   automate  ~40 cr/call  (prior)

An agent checking tabstack usage --json | jq .estimated can gate expensive calls:

CREDITS=$(tabstack usage --json | jq .estimated)
[ "$CREDITS" -gt 500 ] && tabstack research "..." || echo "low credits, skipping"

Scaffolding note

This is explicitly scaffolding. The comment in src/usage.ts says it plainly: “Scaffolding until Tabstack exposes GET /v1/usage.” The day Tabstack ships a balance endpoint or an x-tokens-used response header, the ledger gets fed truth instead of inference. The interface stays the same; the data source improves.