Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 39 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
**84% fewer tokens on JSON tool results. 53% fewer tokens on tool-heavy requests. Sub-300ms semantic cache hits. Zero code changes.**

[![npm version](https://img.shields.io/npm/v/lynkr.svg)](https://www.npmjs.com/package/lynkr)
[![Tests](https://img.shields.io/badge/tests-1041%20passing-brightgreen)](https://github.com/Fast-Editor/Lynkr)
[![Tests](https://img.shields.io/badge/tests-1249%20passing-brightgreen)](https://github.com/Fast-Editor/Lynkr)
[![License: Apache 2.0](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE)
[![Node.js](https://img.shields.io/badge/node-20%2B-green)](https://nodejs.org)
[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/Fast-Editor/Lynkr)
Expand Down Expand Up @@ -326,12 +326,45 @@ the local telemetry store:
- **Routing accuracy** — over-/under-provisioned request counts, a self-audit
of the tier router's decisions
- **Request logs** — filterable by provider, tier, and errors, with latency,
tokens, and cost per request
tokens, and cost per request; every session id links to a drill-down
- **Provider health** — configured providers, credential warnings, circuit
breaker states
- **Insights** — analyzer findings ranked by *past overspend* (auditable
against spend already recorded, never projected savings): dead tool
schemas paying rent, harness side-traffic served on expensive tiers,
weak prompt-cache hit ratios, and Wilson-bounded tier-downsize verdicts.
Each finding ships its evidence rows, the fix, the caveat, and how the
figure was computed. "Not measured yet" is rendered distinctly from
zero — an un-run scan is not an all-clear.
- **Explore** — a Metric × Dimension (× Stack) pivot over routing telemetry
(spend/tokens/requests/sessions by provider, model, tier, request type,
day…) with preset views, period-over-period deltas, and CSV export
- **Session drill-down** — per-model cost mix and an input-context-per-call
chart where distillation and cache behavior are directly visible
- **Cache receipts** — projected switch/hold economics from cache-aware
routing next to the *measured* companion (dollars saved by cache reads
actually served)

JSON APIs behind it
(`/dashboard/api/overview|usage|routing|logs|recommendations|analytics|sessions/:id|statusline`)
if you want the raw numbers.

### Status line for Claude Code

One line after every turn — routed tier, served model, today's spend, and
cache re-read share — with zero token cost (out-of-band, never enters model
context). Add to `~/.claude/settings.json`:

```json
"statusLine": { "type": "command", "command": "lynkr-statusline" }
```

```
◆ Opus 4.8 · MEDIUM → minimax-m3:cloud (ollama) · today $0.06 · cache 67%
```

JSON APIs behind it (`/dashboard/api/overview|usage|routing|logs`) if you want
the raw numbers.
Always prints exactly one line and always exits 0 — a down proxy can never
break the harness. Set `LYNKR_PORT` if Lynkr isn't on 8081.

### Cost tracking & model pricing
Per-request cost is computed from a model-pricing registry (LiteLLM → models.dev,
Expand Down Expand Up @@ -551,7 +584,8 @@ With tier routing + token optimization: **additional 50-87% savings** on cloud p
| **TOON JSON compression** | ✅ up to 87.6% | ❌ | ❌ | ❌ |
| **Upstream SSE streaming** | ✅ native passthrough + cross-format transform | ⚠️ passthrough only | ✅ | ⚠️ |
| **Semantic cache** | ✅ 171ms hits, 0 tokens | ❌ | ❌ | ✅ Prompt cache only |
| **Savings & routing dashboard** | ✅ spend, savings vs flagship, tier mix, routing accuracy, request logs | ⚠️ spend UI only | ⚠️ usage page | ✅ observability suite (no routing accuracy) |
| **Savings & routing dashboard** | ✅ spend, savings vs flagship, tier mix, routing accuracy, request logs, pivot explorer, session drill-down | ⚠️ spend UI only | ⚠️ usage page | ✅ observability suite (no routing accuracy) |
| **Cost recommendations** | ✅ evidence-backed findings (dead tool schemas, side-traffic, cache weak spots, Wilson-proven downsizes) ranked by past overspend | ❌ | ❌ | ⚠️ alerts, no findings |
| **Long-term memory** | ✅ SQLite, per-session | ❌ | ❌ | ❌ |
| **MCP integration** | ✅ | ❌ | ❌ | ❌ |
| **Self-hosted** | ✅ Node.js only | ✅ Python stack | ❌ SaaS | ✅ Docker |
Expand Down
80 changes: 80 additions & 0 deletions bin/statusline.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#!/usr/bin/env node
/**
* lynkr statusline — zero-token Claude Code status line.
*
* Wire into ~/.claude/settings.json:
* "statusLine": { "type": "command", "command": "lynkr-statusline" }
*
* Claude Code pipes session JSON on stdin after each turn. This command:
* - never enters model context (out-of-band — an in-loop MCP integration
* was measured at +36% model-weighted tokens by TokenJam; don't do that)
* - makes ONE local HTTP call to the Lynkr dashboard API with a hard
* 250ms timeout
* - always exits 0 and always prints exactly one line, no matter what
* fails — a broken status line must never break the harness.
*
* Output: ◆ <model> · <tier> → <served model> (<provider>)[ · pin] · today $X · cache NN%
*/

'use strict';

const PORT = Number(process.env.LYNKR_PORT) || Number(process.env.PORT) || 8081;
const TIMEOUT_MS = 250;

function readStdin(maxMs) {
return new Promise((resolve) => {
let data = '';
const timer = setTimeout(() => resolve(data), maxMs);
process.stdin.on('data', (c) => { data += c; });
process.stdin.on('end', () => { clearTimeout(timer); resolve(data); });
process.stdin.on('error', () => { clearTimeout(timer); resolve(data); });
});
}

async function fetchStatus() {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
try {
const res = await fetch(`http://127.0.0.1:${PORT}/dashboard/api/statusline`, {
signal: controller.signal,
});
if (!res.ok) return null;
return await res.json();
} catch {
return null;
} finally {
clearTimeout(timer);
}
}

async function main() {
const [stdinRaw, status] = await Promise.all([readStdin(150), fetchStatus()]);

let clientModel = null;
try {
const parsed = JSON.parse(stdinRaw);
clientModel = parsed?.model?.display_name || parsed?.model?.id || null;
} catch { /* stdin absent or non-JSON — fine */ }

const parts = [];
if (clientModel) parts.push(clientModel);

if (status?.last) {
const served = `${status.last.tier || '?'} → ${status.last.model || '?'} (${status.last.provider || '?'})`;
parts.push(served + (status.last.pinned ? ' · pin' : ''));
} else {
parts.push('lynkr: no traffic yet');
}
if (status && typeof status.todaySpendUsd === 'number') {
parts.push(`today $${status.todaySpendUsd.toFixed(2)}`);
}
if (status && typeof status.cacheReadPct === 'number') {
parts.push(`cache ${status.cacheReadPct}%`);
}

process.stdout.write('◆ ' + parts.join(' · ') + '\n');
}

main()
.catch(() => { process.stdout.write('◆ lynkr statusline unavailable\n'); })
.finally(() => process.exit(0));
21 changes: 21 additions & 0 deletions documentation/token-optimization.md
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,27 @@ curl http://localhost:8081/metrics | grep lynkr_tokens
# lynkr_tokens_cached_total 500000
```

### Insights — where the remaining waste is

The dashboard's **Insights** tab (`/dashboard/api/recommendations`) runs
analyzers over routing telemetry and reports *past overspend* — figures you
can check against spend already recorded, never projections:

- **deadweight** — sessions that carried tool schemas on every request and
never called a tool (schema rent, priced per session)
- **side-requests** — harness bookkeeping (title generation, topic
detection) served above the SIMPLE tier
- **cache-weakspots** — prompt-cache hit ratio per (provider, model), from
per-request cache counters; weak pairs priced as an explicit ceiling
- **downsize** — request types where the tier below has statistically
proven itself (Wilson 95% lower bound ≥ 0.7 on ≥ 20 quality-scored
samples — a raw 70% average on a small sample stays "unproven")

Each finding carries its evidence rows, the fix, a caveat, and how the
figure was computed. The Routing tab additionally shows the **measured**
cache companion — dollars saved by cache reads actually served — beside the
projected switch/hold receipts from cache-aware routing.

### Per-Request Logging

```bash
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
"main": "index.js",
"bin": {
"lynkr": "bin/cli.js",
"lynkr-setup": "scripts/setup.js"
"lynkr-setup": "scripts/setup.js",
"lynkr-statusline": "bin/statusline.js"
},
"scripts": {
"postinstall": "node scripts/check-native.js",
Expand All @@ -36,7 +37,7 @@
"dev": "nodemon index.js",
"lint": "eslint src index.js",
"test": "npm run test:unit && npm run test:performance",
"test:unit": "DATABRICKS_API_KEY=test-key DATABRICKS_API_BASE=http://test.com LOG_FILE_ENABLED=false node --test test/routing.test.js test/hybrid-routing-integration.test.js test/retry-logic.test.js test/sse-transformer.test.js test/passthrough-stream.test.js test/passthrough-mode.test.js test/openrouter-error-resilience.test.js test/format-conversion.test.js test/azure-openai-config.test.js test/azure-openai-format-conversion.test.js test/azure-openai-routing.test.js test/azure-openai-streaming.test.js test/azure-openai-error-resilience.test.js test/azure-openai-integration.test.js test/openai-integration.test.js test/toon-compression.test.js test/gcf-compression.test.js test/llamacpp-integration.test.js test/resilience.test.js test/telemetry-routing.test.js test/memory/store.test.js test/memory/surprise.test.js test/memory/extractor.test.js test/memory/search.test.js test/memory/retriever.test.js test/memory/distiller.test.js test/memory/distiller-freeze.test.js test/memory/wiki.test.js test/memory/skills-cache.test.js test/memory/tencentdb-launcher.test.js test/distill.test.js test/large-payload.test.js test/prompt-cache-injection.test.js test/risk-analyzer.test.js test/interaction-block.test.js test/preflight.test.js test/token-reduction.test.js test/session-affinity.test.js test/cache-state.test.js test/cache-switch-cost.test.js test/model-registry-cost.test.js test/output-format-guard.test.js test/tier-fallback.test.js test/wrap.test.js test/init.test.js test/tool-call-response-metadata.test.js test/degradation.test.js test/routing-telemetry-columns.test.js test/sticky-routing.test.js test/knn-ambiguous-escalate.test.js test/deescalator.test.js test/client-profiles.test.js test/strip-internal-fields.test.js test/complexity-tool-subtraction.test.js test/bandit.test.js test/routing-propensity.test.js test/reward-pipeline.test.js test/knn-cold-start.test.js test/calibration.test.js test/feedback-loop.test.js test/session-fingerprint.test.js test/side-request-guards.test.js test/verifier.test.js test/intent-score.test.js test/difficulty-classifier.test.js test/classifier-setup.test.js test/usage-stats.test.js test/loop-guard.test.js test/moonshot-model-mapping.test.js",
"test:unit": "DATABRICKS_API_KEY=test-key DATABRICKS_API_BASE=http://test.com LOG_FILE_ENABLED=false node --test test/routing.test.js test/hybrid-routing-integration.test.js test/retry-logic.test.js test/sse-transformer.test.js test/passthrough-stream.test.js test/passthrough-mode.test.js test/openrouter-error-resilience.test.js test/format-conversion.test.js test/azure-openai-config.test.js test/azure-openai-format-conversion.test.js test/azure-openai-routing.test.js test/azure-openai-streaming.test.js test/azure-openai-error-resilience.test.js test/azure-openai-integration.test.js test/openai-integration.test.js test/toon-compression.test.js test/gcf-compression.test.js test/llamacpp-integration.test.js test/resilience.test.js test/telemetry-routing.test.js test/memory/store.test.js test/memory/surprise.test.js test/memory/extractor.test.js test/memory/search.test.js test/memory/retriever.test.js test/memory/distiller.test.js test/memory/distiller-freeze.test.js test/memory/wiki.test.js test/memory/skills-cache.test.js test/memory/tencentdb-launcher.test.js test/distill.test.js test/large-payload.test.js test/prompt-cache-injection.test.js test/risk-analyzer.test.js test/interaction-block.test.js test/preflight.test.js test/token-reduction.test.js test/session-affinity.test.js test/cache-state.test.js test/cache-switch-cost.test.js test/lens-recommendations.test.js test/model-registry-cost.test.js test/output-format-guard.test.js test/tier-fallback.test.js test/wrap.test.js test/init.test.js test/tool-call-response-metadata.test.js test/degradation.test.js test/routing-telemetry-columns.test.js test/sticky-routing.test.js test/knn-ambiguous-escalate.test.js test/deescalator.test.js test/client-profiles.test.js test/strip-internal-fields.test.js test/complexity-tool-subtraction.test.js test/bandit.test.js test/routing-propensity.test.js test/reward-pipeline.test.js test/knn-cold-start.test.js test/calibration.test.js test/feedback-loop.test.js test/session-fingerprint.test.js test/side-request-guards.test.js test/verifier.test.js test/intent-score.test.js test/difficulty-classifier.test.js test/classifier-setup.test.js test/usage-stats.test.js test/loop-guard.test.js test/moonshot-model-mapping.test.js",
"test:memory": "DATABRICKS_API_KEY=test-key DATABRICKS_API_BASE=http://test.com node --test test/memory/store.test.js test/memory/surprise.test.js test/memory/extractor.test.js test/memory/search.test.js test/memory/retriever.test.js test/memory/distiller.test.js test/memory/distiller-freeze.test.js test/memory/wiki.test.js test/memory/skills-cache.test.js test/memory/tencentdb-launcher.test.js",
"test:new-features": "DATABRICKS_API_KEY=test-key DATABRICKS_API_BASE=http://test.com node --test test/passthrough-mode.test.js test/openrouter-error-resilience.test.js test/format-conversion.test.js",
"test:performance": "DATABRICKS_API_KEY=test-key DATABRICKS_API_BASE=http://test.com node test/hybrid-routing-performance.test.js && DATABRICKS_API_KEY=test-key DATABRICKS_API_BASE=http://test.com node test/performance-tests.js",
Expand Down
Loading
Loading