Skip to content

feat: cost savings calculator — show tokens, cost, time saved after replay#62

Merged
risjai merged 5 commits into
masterfrom
feat/cost-savings-calculator
Apr 13, 2026
Merged

feat: cost savings calculator — show tokens, cost, time saved after replay#62
risjai merged 5 commits into
masterfrom
feat/cost-savings-calculator

Conversation

@risjai

@risjai risjai commented Apr 13, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • B1: New pricing.rs module in rewind-proxy with hardcoded price table (~10 models: gpt-4o, claude-sonnet, etc.), estimate_cost(), compute_savings(), and ReplaySavings struct. 12 unit tests.
  • B2: rewind show displays a "Replay Savings" section for sessions with forked timelines — cached steps, tokens saved, estimated cost, time saved. Cumulative across multiple forks.
  • B3: Python Recorder tracks cached steps/tokens/duration during fork-and-execute replay, prints savings summary to stderr when replay() exits. Mirrors the Rust price table. 10 new tests.
  • B4: GET /api/sessions/{id}/savings web endpoint returns cumulative savings JSON. docs/replay-and-forking.md updated with Replay Savings section (CLI, SDK, API).

Test plan

  • 12 Rust pricing tests: known model lookups, unknown model fallback, zero tokens, case insensitivity, savings computation, rounding
  • 10 Python tests: pricing lookups, cost-from-steps, savings counter tracking on cache hits, no-replay-mode baseline
  • cargo clippy --workspace -- -D warnings clean
  • ruff check . clean
  • Full workspace Rust tests pass (243 total, 0 failures)
  • Full Python test suite passes (240/242, 2 pre-existing failures in circuit breaker unrelated to this PR)

🤖 Generated with Claude Code

risjai and others added 4 commits April 13, 2026 13:24
…lculator

Implements B1 of the GTM sprint — hardcoded price table for ~10 LLM models
(gpt-4o, claude-sonnet, etc.) with estimate_cost() and compute_savings()
functions. ReplaySavings struct summarizes tokens, cost, and time saved
when fork-and-execute serves cached steps instead of re-running.

12 unit tests covering price lookups, cost calculation, and edge cases.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When a session has forked timelines (from fork-and-execute replay),
`rewind show` now displays a "Replay Savings" section showing cached
steps, tokens saved, estimated cost saved, and time saved. Cumulative
savings are shown when a session has multiple forks.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
After fork-and-execute replay exits, prints cached steps, tokens saved,
estimated cost, and time saved to stderr. Mirrors the Rust pricing table
with the same ~10 model price entries.

Adds _cached_steps_count, _cached_tokens, _cached_duration_ms counters
to Recorder, incremented in _try_replay_cached(). estimate_cost() and
_estimate_cost_from_steps() exposed for reuse.

10 new tests: pricing lookups, cost-from-steps, savings counter tracking.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds a web API endpoint that returns cumulative replay savings for a
session (steps cached, tokens saved, estimated cost, time saved).
Computes savings by comparing parent timeline steps against forked
timeline boundaries.

Updates docs/replay-and-forking.md with Replay Savings section
covering CLI, Python SDK, and API usage.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@risjai

risjai commented Apr 13, 2026

Copy link
Copy Markdown
Collaborator Author

Code Review: Cost Savings Calculator (Feature B)

Overall: Clean implementation across all 4 tasks. 1 architectural concern, 1 minor bug, 2 nits.


Architectural Concern (P1)

1. rewind-web now depends on rewind-proxy — this creates a circular risk

crates/rewind-web/Cargo.toml adds rewind-proxy as a dependency so the web API handler can call rewind_proxy::pricing::compute_savings. Today this is fine — rewind-proxy doesn't depend on rewind-web. But the proxy and web server are architecturally separate concerns (proxy = transparent HTTP middleman, web = dashboard API). If rewind-proxy ever needs to call a web API (e.g., to notify the dashboard), you'll have a circular dependency.

Fix: Move pricing.rs to rewind-store (or a new rewind-common crate). The pricing module only depends on rewind_store::Step — it has no proxy-specific logic. Then both rewind-proxy, rewind-cli, and rewind-web can use it without cross-crate coupling.

Not blocking for this PR, but worth a follow-up refactor before the crate graph gets more tangled.


Minor Bug (P2)

2. Python _print_replay_savings computes cost from ALL parent steps, not just cached ones

python/rewind_agent/recorder.py_print_replay_savings calls:

cost = _estimate_cost_from_steps(self._replay_steps or [], self._fork_at_step or 0)

_estimate_cost_from_steps filters steps where step_number <= fork_at_step. But self._replay_steps is the FULL parent timeline (all steps), and it correctly filters. However, if some steps within the fork range were skipped (e.g., because response_blob was empty — the guard added in PR #58), they would NOT have been served from cache, but _estimate_cost_from_steps would still count them.

The Rust side (compute_savings) takes explicitly separated cached_steps and live_steps vectors, avoiding this issue. The Python side should use the tracked self._cached_tokens and self._cached_duration_ms counters (which only increment on actual cache hits) instead of re-computing from the step list.

Suggested fix:

cost = estimate_cost("default", self._cached_tokens // 2, self._cached_tokens // 2)

Or better: track _cached_cost directly in _try_replay_cached alongside the other counters.


Nits (P3)

3. time_saved_ms is integer division — loses sub-second precision in CLI output

crates/rewind-cli/src/main.rs — The time display uses integer division:

let secs = cumulative.time_saved_ms / 1000;

For 1500ms, this shows 1.5s (correct, due to the modulo calculation). But for 800ms it shows 0.8s which is fine. Actually this is correct — no issue here. Withdrawing this nit.

4. Duplicated savings computation logic in CLI and web API

Both print_session_savings (CLI) and get_session_savings (web API) load timelines, filter forks, load parent/own steps, and call compute_savings with identical logic. Consider extracting a shared function in the store or pricing module:

pub fn session_savings(store: &Store, session_id: &str) -> Result<ReplaySavings>

Both call sites then become one-liners. Not blocking.


What's Done Well

  • Price table is sensible — covers the top 10 models, case-insensitive matching with substring containment (s.contains("gpt-4o")), sensible $1/$3 default fallback
  • Match order is correctgpt-4o-mini before gpt-4o prevents the shorter match from shadowing the longer one
  • Python mirrors Rust exactly — same models, same prices, same containment matching
  • Savings tracking in Recorder — clean: three atomic counters incremented in _try_replay_cached, printed on unpatch_all, doesn't interfere with the existing replay logic
  • 22 tests total (12 Rust + 10 Python) — good coverage of known models, unknown fallback, zero tokens, case insensitivity, cumulative savings
  • Non-intrusive — savings only show when forks exist, Python prints to stderr (doesn't pollute stdout), CLI adds a section to rewind show without changing existing output

Summary

# Severity Issue
1 Architectural rewind-webrewind-proxy dependency coupling (move pricing.rs to rewind-store)
2 Minor bug Python cost estimate counts fork-range steps, not actual cache hits
3 Nit Duplicated savings computation in CLI + web API

Fix #2 (use tracked counters instead of re-computing from step list) before merge. #1 and #3 are follow-up refactors.

… cost

Addresses review feedback on PR #62:

P1: Moved pricing.rs from rewind-proxy to rewind-store so rewind-web
no longer depends on rewind-proxy. rewind-proxy re-exports for
backwards compat. Removes the cross-concern dependency coupling.

P2: Python _print_replay_savings now uses self._cached_cost (tracked
per actual cache hit in _try_replay_cached) instead of re-computing
from the full parent step list. Fixes bug where steps with empty
response_blob would be counted as savings despite not being served.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@risjai
risjai merged commit 424ad38 into master Apr 13, 2026
4 checks passed
@risjai
risjai deleted the feat/cost-savings-calculator branch April 13, 2026 08:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant