ctxoptimizer 0.4.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. ctxoptimizer/__init__.py +5 -0
  2. ctxoptimizer/__main__.py +14 -0
  3. ctxoptimizer/admin_security.py +130 -0
  4. ctxoptimizer/analysis/__init__.py +1 -0
  5. ctxoptimizer/analysis/baseline.py +69 -0
  6. ctxoptimizer/analysis/break_even.py +49 -0
  7. ctxoptimizer/analysis/drift.py +151 -0
  8. ctxoptimizer/analysis/edits.py +148 -0
  9. ctxoptimizer/analysis/markers.py +34 -0
  10. ctxoptimizer/analysis/recommend.py +114 -0
  11. ctxoptimizer/analysis/segments.py +57 -0
  12. ctxoptimizer/analysis/session.py +62 -0
  13. ctxoptimizer/analysis/state.py +239 -0
  14. ctxoptimizer/analysis/tokens.py +62 -0
  15. ctxoptimizer/analysis/waste.py +322 -0
  16. ctxoptimizer/cli.py +873 -0
  17. ctxoptimizer/config.py +512 -0
  18. ctxoptimizer/dashboard/__init__.py +1 -0
  19. ctxoptimizer/dashboard/api.py +537 -0
  20. ctxoptimizer/dashboard/service.py +157 -0
  21. ctxoptimizer/dashboard/static/.gitkeep +1 -0
  22. ctxoptimizer/dashboard/templates/.gitkeep +1 -0
  23. ctxoptimizer/dashboard/templates/doctor.html +46 -0
  24. ctxoptimizer/dashboard/templates/live.html +91 -0
  25. ctxoptimizer/dashboard/templates/overview.html +172 -0
  26. ctxoptimizer/dashboard/templates/pricing.html +115 -0
  27. ctxoptimizer/dashboard/templates/request.html +111 -0
  28. ctxoptimizer/dashboard/templates/session.html +67 -0
  29. ctxoptimizer/dashboard/templates/settings.html +79 -0
  30. ctxoptimizer/dashboard/templates/table.html +56 -0
  31. ctxoptimizer/dashboard/templates/transforms.html +118 -0
  32. ctxoptimizer/dashboard/templates/waste.html +120 -0
  33. ctxoptimizer/dashboard/views.py +429 -0
  34. ctxoptimizer/data/cache_limits.json +23 -0
  35. ctxoptimizer/data/default_config.toml +185 -0
  36. ctxoptimizer/data/pricing.json +421 -0
  37. ctxoptimizer/doctor.py +129 -0
  38. ctxoptimizer/guard.py +139 -0
  39. ctxoptimizer/ids.py +73 -0
  40. ctxoptimizer/log.py +73 -0
  41. ctxoptimizer/mcp.py +124 -0
  42. ctxoptimizer/models.py +145 -0
  43. ctxoptimizer/pipeline.py +126 -0
  44. ctxoptimizer/profiles.py +152 -0
  45. ctxoptimizer/providers/__init__.py +37 -0
  46. ctxoptimizer/providers/anthropic.py +105 -0
  47. ctxoptimizer/providers/base.py +191 -0
  48. ctxoptimizer/providers/gemini.py +143 -0
  49. ctxoptimizer/providers/ollama.py +133 -0
  50. ctxoptimizer/providers/openai_chat.py +135 -0
  51. ctxoptimizer/providers/openai_responses.py +121 -0
  52. ctxoptimizer/proxy.py +665 -0
  53. ctxoptimizer/routing.py +259 -0
  54. ctxoptimizer/server.py +319 -0
  55. ctxoptimizer/service.py +150 -0
  56. ctxoptimizer/telemetry/__init__.py +1 -0
  57. ctxoptimizer/telemetry/blobs.py +57 -0
  58. ctxoptimizer/telemetry/expand.py +62 -0
  59. ctxoptimizer/telemetry/pricing.py +168 -0
  60. ctxoptimizer/telemetry/redact.py +53 -0
  61. ctxoptimizer/telemetry/sse.py +145 -0
  62. ctxoptimizer/telemetry/store.py +829 -0
  63. ctxoptimizer/telemetry/usage.py +435 -0
  64. ctxoptimizer/transforms/__init__.py +87 -0
  65. ctxoptimizer/transforms/ansi.py +21 -0
  66. ctxoptimizer/transforms/anthropic_cache.py +100 -0
  67. ctxoptimizer/transforms/backref.py +74 -0
  68. ctxoptimizer/transforms/base.py +93 -0
  69. ctxoptimizer/transforms/cache_serve.py +146 -0
  70. ctxoptimizer/transforms/dedupe_lines.py +69 -0
  71. ctxoptimizer/transforms/eligibility.py +24 -0
  72. ctxoptimizer/transforms/openai_hints.py +84 -0
  73. ctxoptimizer/transforms/progress.py +52 -0
  74. ctxoptimizer/transforms/stale_result_elision.py +109 -0
  75. ctxoptimizer/transforms/truncate.py +100 -0
  76. ctxoptimizer/transforms/volatile.py +177 -0
  77. ctxoptimizer/transforms/whitespace.py +25 -0
  78. ctxoptimizer-0.4.1.dist-info/METADATA +196 -0
  79. ctxoptimizer-0.4.1.dist-info/RECORD +83 -0
  80. ctxoptimizer-0.4.1.dist-info/WHEEL +4 -0
  81. ctxoptimizer-0.4.1.dist-info/entry_points.txt +3 -0
  82. ctxoptimizer-0.4.1.dist-info/licenses/LICENSE +21 -0
  83. ctxoptimizer-0.4.1.dist-info/licenses/NOTICE +13 -0
@@ -0,0 +1,429 @@
1
+ """Server-rendered dashboard views (SPEC section 16.2)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from dataclasses import asdict
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+ from starlette.requests import Request
11
+ from starlette.responses import Response
12
+ from starlette.templating import Jinja2Templates
13
+
14
+ from ctxoptimizer import __version__
15
+ from ctxoptimizer.analysis.recommend import recommendations
16
+ from ctxoptimizer.config import data_dir
17
+ from ctxoptimizer.dashboard.service import (
18
+ findings_from_rows,
19
+ parse_since,
20
+ pricing_payload,
21
+ )
22
+ from ctxoptimizer.doctor import run_checks
23
+ from ctxoptimizer.telemetry.expand import expand_stats
24
+
25
+ templates = Jinja2Templates(directory=str(Path(__file__).parent / "templates"))
26
+
27
+
28
+ async def overview(request: Request) -> Response:
29
+ state = request.app.state
30
+ store = getattr(state, "store", None)
31
+ period = request.query_params.get("period", "7d")
32
+ since = parse_since(period) if period != "all" else None
33
+ summary = (
34
+ store.summary(since)
35
+ if store is not None
36
+ else {
37
+ "requests": 0,
38
+ "cost_usd": 0.0,
39
+ "baseline_usd": 0.0,
40
+ "fresh_input": 0,
41
+ "cache_read": 0,
42
+ "output_tokens": 0,
43
+ "tokens_removed": 0,
44
+ }
45
+ )
46
+ rows = store.export_rows(since, 20) if store is not None else []
47
+ projects = store.group_stats("project", 10, since) if store is not None else []
48
+ models = store.group_stats("model", 10, since) if store is not None else []
49
+ points = store.timeseries("cost", "day", since) if store is not None else []
50
+ waste = (
51
+ store.waste_summary(since)
52
+ if store is not None
53
+ else {
54
+ "findings": 0,
55
+ "tokens": 0,
56
+ "cost_usd": 0.0,
57
+ }
58
+ )
59
+ waste_points = store.waste_timeseries("day", since) if store is not None else []
60
+ spend_line = _polyline([float(point.get("value") or 0) for point in points])
61
+ waste_line = _polyline([float(point.get("cost_usd") or 0) for point in waste_points])
62
+ return templates.TemplateResponse(
63
+ request,
64
+ "overview.html",
65
+ {
66
+ "version": __version__,
67
+ "mode": state.config.mode.default,
68
+ "summary": summary,
69
+ "rows": rows,
70
+ "projects": projects,
71
+ "models": models,
72
+ "points": points,
73
+ "period": period,
74
+ "privacy": state.config.telemetry.capture_payloads != "off",
75
+ "capture": state.config.telemetry.capture_payloads,
76
+ "waste": waste,
77
+ "waste_points": waste_points,
78
+ "spend_line": spend_line,
79
+ "waste_line": waste_line,
80
+ },
81
+ )
82
+
83
+
84
+ async def waste_view(request: Request) -> Response:
85
+ state = request.app.state
86
+ store = getattr(state, "store", None)
87
+ analysis = getattr(state, "analysis", None)
88
+ findings = analysis.findings() if analysis is not None else []
89
+ rows = store.fetch_waste() if store is not None else []
90
+ if not rows:
91
+ rows = [_finding_dict(finding) for finding in findings]
92
+ if not findings and rows:
93
+ findings = findings_from_rows(rows)
94
+ items = recommendations(
95
+ findings,
96
+ cache_serve_enabled=state.config.transforms.cache_serve.enabled,
97
+ volatile_normalizer_enabled=state.config.transforms.volatile_normalizer.enabled,
98
+ transforms_enabled=True,
99
+ )
100
+ suppressed = store.suppressed_recommendations() if store is not None else set()
101
+ items = [item for item in items if item["id"] not in suppressed]
102
+ return templates.TemplateResponse(
103
+ request,
104
+ "waste.html",
105
+ {
106
+ "version": __version__,
107
+ "mode": state.config.mode.default,
108
+ "findings": rows,
109
+ "recommendations": items,
110
+ "csrf": state.csrf_token,
111
+ },
112
+ )
113
+
114
+
115
+ async def sessions_view(request: Request) -> Response:
116
+ store = getattr(request.app.state, "store", None)
117
+ rows = store.fetch_sessions() if store is not None else []
118
+ return _table(
119
+ request,
120
+ "Sessions",
121
+ "Grouped by derived session id.",
122
+ [
123
+ ("session_id", "Session"),
124
+ ("project", "Project"),
125
+ ("client", "Client"),
126
+ ("provider", "Provider"),
127
+ ("request_count", "Requests"),
128
+ ("last_ts", "Last seen"),
129
+ ],
130
+ rows,
131
+ )
132
+
133
+
134
+ async def session_view(request: Request) -> Response:
135
+ store = getattr(request.app.state, "store", None)
136
+ session_id = request.path_params["session_id"]
137
+ rows = (
138
+ list(reversed(store.fetch_session_requests(session_id, 500))) if store is not None else []
139
+ )
140
+ drift = store.fetch_drift(500) if store is not None else []
141
+ by_request = {item["request_id"]: item for item in drift}
142
+ for row in rows:
143
+ row["drift"] = (by_request.get(row["request_id"]) or {}).get("cause")
144
+ ratios = [
145
+ 100
146
+ * (row.get("cache_read") or 0)
147
+ / max((row.get("cache_read") or 0) + (row.get("fresh_input") or 0), 1)
148
+ for row in rows
149
+ ]
150
+ return templates.TemplateResponse(
151
+ request,
152
+ "session.html",
153
+ {
154
+ "version": __version__,
155
+ "mode": request.app.state.config.mode.default,
156
+ "title": f"Session {session_id}",
157
+ "session_id": session_id,
158
+ "rows": rows,
159
+ "ratio_line": _polyline(ratios),
160
+ },
161
+ )
162
+
163
+
164
+ async def requests_view(request: Request) -> Response:
165
+ store = getattr(request.app.state, "store", None)
166
+ rows = store.fetch_recent(100) if store is not None else []
167
+ return _table(
168
+ request,
169
+ "Requests",
170
+ "Most recent 100 requests.",
171
+ [
172
+ ("request_id", "Request"),
173
+ ("ts", "Time"),
174
+ ("client", "Client"),
175
+ ("provider", "Provider"),
176
+ ("model", "Model"),
177
+ ("fresh_input", "Fresh"),
178
+ ("cache_read", "Cache read"),
179
+ ("output_tokens", "Output"),
180
+ ("cost_actual_usd", "Cost"),
181
+ ("outcome", "Outcome"),
182
+ ],
183
+ rows,
184
+ )
185
+
186
+
187
+ async def request_view(request: Request) -> Response:
188
+ store = getattr(request.app.state, "store", None)
189
+ request_id = request.path_params["request_id"]
190
+ row = store.fetch_request(request_id) if store is not None else None
191
+ reports = store.fetch_transform_reports(request_id) if store is not None else []
192
+ diff = _diff_blocks(request, request_id)
193
+ status_code = 200 if row is not None else 404
194
+ usage = {}
195
+ timing = {}
196
+ headers: dict[str, Any] = {}
197
+ if row is not None:
198
+ try:
199
+ headers = json.loads(row.get("request_headers_json") or "{}")
200
+ except (TypeError, json.JSONDecodeError):
201
+ headers = {}
202
+ usage = {
203
+ key.removeprefix("cache_write_"): row[key]
204
+ for key in row
205
+ if key.startswith("cache_write_")
206
+ }
207
+ usage.update(
208
+ {
209
+ "source": row.get("usage_source"),
210
+ "fresh_input": row.get("fresh_input"),
211
+ "cache_read": row.get("cache_read"),
212
+ "output": row.get("output_tokens"),
213
+ "reasoning": row.get("reasoning_tokens"),
214
+ "provider_cost_usd": row.get("provider_cost_usd"),
215
+ }
216
+ )
217
+ timing = {
218
+ "ttfb_ms": row.get("ttfb_ms"),
219
+ "total_ms": row.get("total_ms"),
220
+ "proxy_overhead_ms": row.get("proxy_overhead_ms"),
221
+ }
222
+ return templates.TemplateResponse(
223
+ request,
224
+ "request.html",
225
+ {
226
+ "version": __version__,
227
+ "mode": request.app.state.config.mode.default,
228
+ "title": f"Request {request_id}",
229
+ "row": row or {},
230
+ "found": row is not None,
231
+ "reports": reports,
232
+ "diff": diff,
233
+ "usage": usage,
234
+ "timing": timing,
235
+ "headers": headers,
236
+ },
237
+ status_code=status_code,
238
+ )
239
+
240
+
241
+ def _diff_blocks(request: Request, request_id: str) -> list[dict[str, Any]]:
242
+ store = getattr(request.app.state, "store", None)
243
+ blobs = getattr(request.app.state, "blobs", None)
244
+ if store is None or blobs is None:
245
+ return []
246
+ blocks: list[dict[str, Any]] = []
247
+ for capture in store.fetch_captures(request_id):
248
+ before = blobs.get(capture.get("before_hash") or "")
249
+ after = blobs.get(capture.get("after_hash") or "")
250
+ blocks.append(
251
+ {
252
+ "location": capture.get("location"),
253
+ "before": before.decode("utf-8", "replace") if before else "",
254
+ "after": after.decode("utf-8", "replace") if after else "",
255
+ }
256
+ )
257
+ return blocks
258
+
259
+
260
+ async def transforms_view(request: Request) -> Response:
261
+ state = request.app.state
262
+ store = getattr(state, "store", None)
263
+ guard = getattr(state, "guard", None)
264
+ stats = {item["transform_id"]: item for item in (store.transform_stats() if store else [])}
265
+ quarantined = {item["transform_id"] for item in (guard.status() if guard else [])}
266
+ rows = [
267
+ {
268
+ "transform": name,
269
+ "enabled": value.get("enabled", False),
270
+ "quarantined": name in quarantined,
271
+ "runs": stats.get(name, {}).get("runs", 0),
272
+ "tokens_saved": stats.get(name, {}).get("tokens_saved", 0),
273
+ "avg_ms": round(float(stats.get(name, {}).get("avg_ms") or 0.0), 3),
274
+ "errors": stats.get(name, {}).get("errors", 0),
275
+ "no_regress": stats.get(name, {}).get("no_regress", 0),
276
+ "fallbacks": stats.get(name, {}).get("fallbacks", 0),
277
+ }
278
+ for name, value in state.config.transforms.model_dump().items()
279
+ if isinstance(value, dict)
280
+ ]
281
+ return templates.TemplateResponse(
282
+ request,
283
+ "transforms.html",
284
+ {
285
+ "version": __version__,
286
+ "mode": state.config.mode.default,
287
+ "rows": rows,
288
+ "guard_events": store.fetch_guard_events() if store else [],
289
+ "expand": expand_stats(),
290
+ "csrf": state.csrf_token,
291
+ },
292
+ )
293
+
294
+
295
+ async def pricing_view(request: Request) -> Response:
296
+ config = request.app.state.config
297
+ payload = pricing_payload(config)
298
+ store = getattr(request.app.state, "store", None)
299
+ unpriced = store.unpriced() if store is not None else []
300
+ return templates.TemplateResponse(
301
+ request,
302
+ "pricing.html",
303
+ {
304
+ "version": __version__,
305
+ "mode": config.mode.default,
306
+ "title": f"Pricing ({payload['version']})",
307
+ "models": payload["models"],
308
+ "payload_json": json.dumps({"models": payload["models"]}, indent=2),
309
+ "unpriced": unpriced,
310
+ "csrf": request.app.state.csrf_token,
311
+ },
312
+ )
313
+
314
+
315
+ async def settings_view(request: Request) -> Response:
316
+ config = request.app.state.config
317
+ data: dict[str, Any] = {
318
+ "host": config.server.host,
319
+ "port": config.server.port,
320
+ "allow_remote": config.server.allow_remote,
321
+ "mode": config.mode.default,
322
+ "capture_payloads": config.telemetry.capture_payloads,
323
+ "retention_days": config.telemetry.retention_days,
324
+ "db_path": config.telemetry.db_path or str(data_dir() / "ctxoptimizer.db"),
325
+ "pricing_file": config.pricing.file or "(bundled)",
326
+ "upstreams": {
327
+ name: (entry if isinstance(entry, str) else entry.url)
328
+ for name, entry in config.upstreams.items()
329
+ },
330
+ "config_hash": config.config_hash,
331
+ }
332
+ return templates.TemplateResponse(
333
+ request,
334
+ "settings.html",
335
+ {
336
+ "version": __version__,
337
+ "mode": config.mode.default,
338
+ "title": "Settings",
339
+ "data": data,
340
+ "privacy": config.telemetry.capture_payloads != "off",
341
+ "csrf": request.app.state.csrf_token,
342
+ },
343
+ )
344
+
345
+
346
+ async def live_view(request: Request) -> Response:
347
+ state = request.app.state
348
+ return templates.TemplateResponse(
349
+ request,
350
+ "live.html",
351
+ {
352
+ "version": __version__,
353
+ "mode": state.config.mode.default,
354
+ "counters": state.counters,
355
+ "last": state.last_request or {},
356
+ "quarantined": (
357
+ state.guard.status() if getattr(state, "guard", None) is not None else []
358
+ ),
359
+ },
360
+ )
361
+
362
+
363
+ async def doctor_view(request: Request) -> Response:
364
+ state = request.app.state
365
+ checks = run_checks(state.config, offline=True)
366
+ return templates.TemplateResponse(
367
+ request,
368
+ "doctor.html",
369
+ {
370
+ "version": __version__,
371
+ "mode": state.config.mode.default,
372
+ "title": "Doctor",
373
+ "checks": checks,
374
+ },
375
+ )
376
+
377
+
378
+ def _polyline(values: list[float], width: int = 600, height: int = 80) -> str:
379
+ if len(values) < 2:
380
+ return ""
381
+ maximum = max(max(values), 1e-9)
382
+ step = width / (len(values) - 1)
383
+ return " ".join(
384
+ f"{index * step:.1f},{height - (value / maximum) * height:.1f}"
385
+ for index, value in enumerate(values)
386
+ )
387
+
388
+
389
+ def _finding_dict(finding: Any) -> dict[str, Any]:
390
+ data = asdict(finding)
391
+ data["key_hash"] = finding.key_hash.hex()
392
+ return data
393
+
394
+
395
+ def _table(
396
+ request: Request,
397
+ title: str,
398
+ subtitle: str,
399
+ columns: list[tuple[str, str]],
400
+ rows: list[dict[str, Any]],
401
+ ) -> Response:
402
+ return templates.TemplateResponse(
403
+ request,
404
+ "table.html",
405
+ {
406
+ "version": __version__,
407
+ "mode": request.app.state.config.mode.default,
408
+ "title": title,
409
+ "subtitle": subtitle,
410
+ "columns": columns,
411
+ "rows": rows,
412
+ "csrf": request.app.state.csrf_token,
413
+ },
414
+ )
415
+
416
+
417
+ __all__ = [
418
+ "doctor_view",
419
+ "live_view",
420
+ "overview",
421
+ "pricing_view",
422
+ "request_view",
423
+ "requests_view",
424
+ "session_view",
425
+ "sessions_view",
426
+ "settings_view",
427
+ "transforms_view",
428
+ "waste_view",
429
+ ]
@@ -0,0 +1,23 @@
1
+ {
2
+ "version": "2026-10-24",
3
+ "source": "https://platform.claude.com/docs/en/build-with-claude/prompt-caching",
4
+ "models": [
5
+ {"match": "claude-fable-5-1*", "min_tokens": 512},
6
+ {"match": "claude-mythos-5-1*", "min_tokens": 512},
7
+ {"match": "claude-opus-5-5*", "min_tokens": 512},
8
+ {"match": "claude-opus-5*", "min_tokens": 512},
9
+ {"match": "claude-sonnet-5-5*", "min_tokens": 512},
10
+ {"match": "claude-sonnet-5*", "min_tokens": 1024},
11
+ {"match": "claude-haiku-5-5*", "min_tokens": 512},
12
+ {"match": "claude-fable-5*", "min_tokens": 512},
13
+ {"match": "claude-mythos-5*", "min_tokens": 512},
14
+ {"match": "claude-opus-4-8*", "min_tokens": 1024},
15
+ {"match": "claude-opus-4-7*", "min_tokens": 2048},
16
+ {"match": "claude-opus-4-6*", "min_tokens": 4096},
17
+ {"match": "claude-opus-4-5*", "min_tokens": 4096},
18
+ {"match": "claude-sonnet-4-6*", "min_tokens": 1024},
19
+ {"match": "claude-sonnet-4-5*", "min_tokens": 1024},
20
+ {"match": "claude-haiku-4-5*", "min_tokens": 4096},
21
+ {"match": "claude-haiku-3-5*", "min_tokens": 2048}
22
+ ]
23
+ }
@@ -0,0 +1,185 @@
1
+ # ctxoptimizer configuration (SPEC section 7).
2
+ # Created on first run. Edit and reload with `ctxoptimizer reload` (P5).
3
+
4
+ schema_version = 1
5
+
6
+ [server]
7
+ host = "127.0.0.1"
8
+ port = 8787
9
+ allow_remote = false
10
+ admin_token = ""
11
+ max_body_bytes = 33554432
12
+ log_level = "info"
13
+
14
+ [mode]
15
+ default = "shadow"
16
+
17
+ [upstreams]
18
+ anthropic = "https://api.anthropic.com"
19
+ openai = "https://api.openai.com"
20
+ gemini = "https://generativelanguage.googleapis.com"
21
+ ollama = "http://127.0.0.1:11434"
22
+ # openrouter = { url = "https://openrouter.ai/api", kind = "openai" }
23
+ # deepseek = { url = "https://api.deepseek.com", kind = "openai",
24
+ # usage = "deepseek", cache_policy = "automatic-deepseek" }
25
+ # deepseek-anthropic = { url = "https://api.deepseek.com/anthropic",
26
+ # kind = "anthropic", cache_hints = false }
27
+ # gemini-openai = { url = "https://generativelanguage.googleapis.com/v1beta/openai",
28
+ # kind = "openai", usage = "gemini-openai" }
29
+ # ollama-openai = { url = "http://127.0.0.1:11434/v1", kind = "openai" }
30
+
31
+ [http]
32
+ connect_timeout_s = 10
33
+ write_timeout_s = 60
34
+ pool_timeout_s = 30
35
+ read_timeout_s = 0
36
+ http2 = true
37
+ max_connections = 100
38
+ force_identity_encoding = true
39
+
40
+ [pipeline]
41
+ time_budget_ms = 250
42
+ offload_threshold_bytes = 262144
43
+ min_saving_tokens = 20
44
+ retry_original_on_status = [400, 422]
45
+
46
+ [transforms.ansi_strip]
47
+ enabled = true
48
+
49
+ [transforms.progress_collapse]
50
+ enabled = true
51
+
52
+ [transforms.line_dedupe]
53
+ enabled = true
54
+ min_repeat = 3
55
+
56
+ [transforms.whitespace_trim]
57
+ enabled = true
58
+ max_blank_run = 2
59
+
60
+ [transforms.smart_truncate]
61
+ enabled = true
62
+ trigger_bytes = 16384
63
+ target_bytes = 10240
64
+ head_lines = 40
65
+ tail_lines = 60
66
+ context_lines = 3
67
+ max_line_chars = 2000
68
+ max_signal_lines = 150
69
+
70
+ [transforms.duplicate_backref]
71
+ enabled = true
72
+ min_bytes = 2048
73
+ include_never_touch = true
74
+
75
+ [transforms.stale_result_elision]
76
+ enabled = false
77
+ keep_recent_turns = 12
78
+ batch_turns = 8
79
+ min_bytes = 4096
80
+
81
+ [transforms.anthropic_cache]
82
+ enabled = true
83
+ ttl = "5m"
84
+ respect_existing = true
85
+ max_breakpoints = 4
86
+
87
+ [transforms.openai_hints]
88
+ enabled = false
89
+ send_prompt_cache_key = true
90
+ inject_stream_usage = false
91
+
92
+ [transforms.cache_serve]
93
+ enabled = false
94
+ include_never_touch = true
95
+ min_bytes = 2048
96
+ max_stubs_per_request = 6
97
+ batch_turns = 8
98
+ keep_recent_turns = 12
99
+ max_state_sessions = 256
100
+ max_entries_per_session = 512
101
+
102
+ [transforms.volatile_normalizer]
103
+ enabled = false
104
+
105
+ [tools]
106
+ compactable = [
107
+ "*bash*",
108
+ "*shell*",
109
+ "*terminal*",
110
+ "*exec*",
111
+ "*run*",
112
+ "*command*",
113
+ "*test*",
114
+ "*build*",
115
+ "*npm*",
116
+ "*pytest*",
117
+ "*cargo*",
118
+ ]
119
+ never_touch = [
120
+ "*read*",
121
+ "*view*",
122
+ "*cat*",
123
+ "*edit*",
124
+ "*write*",
125
+ "*patch*",
126
+ "*apply*",
127
+ "*notebook*",
128
+ ]
129
+
130
+ [telemetry]
131
+ db_path = ""
132
+ capture_payloads = "changed_blocks"
133
+ retention_days = 14
134
+ redact_secrets = true
135
+ writer_queue_max = 10000
136
+ max_blob_bytes = 524288000
137
+
138
+ [waste]
139
+ enabled = true
140
+ dedupe_window_min = 30
141
+ capture_assistant_hashes = true
142
+
143
+ [waste.redundant_reread]
144
+ enabled = true
145
+ min_bytes = 512
146
+
147
+ [waste.identical_output]
148
+ enabled = true
149
+ min_bytes = 1024
150
+
151
+ [waste.unbatched_fanout]
152
+ enabled = true
153
+ min_calls = 4
154
+
155
+ [waste.runaway_session]
156
+ enabled = true
157
+ idle_gap_s = 900
158
+ min_turns = 6
159
+
160
+ [waste.recurring_drift]
161
+ enabled = true
162
+ min_occurrences = 2
163
+
164
+ [waste.churn_detector]
165
+ enabled = false
166
+
167
+ [waste.schema_bloat]
168
+ enabled = true
169
+ min_session_requests = 10
170
+
171
+ [guard]
172
+ enabled = true
173
+ fallback_window = 50
174
+ fallback_limit = 3
175
+ our_fault_drift_limit = 1
176
+ min_savings_to_stay = 0.01
177
+ notify = "dashboard"
178
+ auto_reenable = 0
179
+ expand_error_rate = 0.20
180
+
181
+ [pricing]
182
+ file = ""
183
+
184
+ [dashboard]
185
+ enabled = true