diff --git a/workplans/LLM-WP-0007-kimi-k3-default-and-spend-reporting.md b/workplans/LLM-WP-0007-kimi-k3-default-and-spend-reporting.md new file mode 100644 index 0000000..4bd2f53 --- /dev/null +++ b/workplans/LLM-WP-0007-kimi-k3-default-and-spend-reporting.md @@ -0,0 +1,291 @@ +--- +id: LLM-WP-0007 +type: workplan +title: "Kimi K3 default via OpenRouter + EUR spend reporting" +domain: agents +repo: llm-connect +status: ready +owner: codex +topic_slug: kimi-k3-default-spend-reporting +planning_priority: high +planning_order: 7 +created: "2026-08-03" +updated: "2026-08-03" +depends_on_workplans: + - LLM-WP-0005 +related_workplans: + - LLM-WP-0006 +--- + +# LLM-WP-0007 — Kimi K3 default via OpenRouter + EUR spend reporting + +**status:** ready +**owner:** codex + +## Purpose + +1. Make **MoonshotAI Kimi K3** (`moonshotai/kimi-k3` on OpenRouter) a first-class + basemodel option, and **promote it to the library default** once a live smoke + call succeeds (rate table, adapter default, config default, docs). +2. Ensure every path that uses **llm-connect from the command line** reports + **token usage and cost in euros** for the work it handled. +3. Make weekly spend easy to inspect: + - **Last week** Monday 00:00 → Sunday 24:00 (local operator timezone, default + Europe/Berlin) + - **Current week** Monday 00:00 → now + +This workplan does **not** change activity-core runtime profiles or K8s deploy +defaults unless those profiles currently inherit the global OpenRouter default +without an explicit model — those stay explicit where they already are. + +## Demand signal + +Operator request (2026-08-03): no open workplans; next priority is evaluating +Kimi K3 as basemodel/default via OpenRouter, plus CLI cost transparency in +tokens and €, plus mon–sun / mon–now weekly spend views. + +## Current repo state (as of 2026-08-03) + +| Area | State | +|---|---| +| Active workplans | **None** — WP-0001…0006 + ADHOC-2026-06-02 all finished/completed | +| OpenRouter default model | `anthropic/claude-sonnet-4` (`openrouter.py`, `config.LLMConfig`) | +| Rate table | USD-only seed of nine models; **no** `moonshotai/kimi-k3` entry | +| Cost model | `estimate_cost` → `CostEstimate` with `cost_usd` only (`costs.py`) | +| CLI (`llm-connect`) | Registry tools only: `rates show`, `classes show`, `classes fit` — **no** prompt execute, **no** spend report | +| Usage persistence | Server optional audit JSON under `LLM_CONNECT_AUDIT_DIR`; quality ledger is for adaptive routing, not operator spend | +| BudgetTracker | Token-cap only; no EUR accounting | + +OpenRouter live listing (checked 2026-08-03): + +- Model id: `moonshotai/kimi-k3` +- Context: 1_048_576 +- List price (USD per token → per 1k): **prompt $0.003 / 1k**, **completion $0.015 / 1k** + +## Architecture sketch + +``` +llm_connect/ + rates.py # + moonshotai/kimi-k3 rate; currency remains list-price USD + costs.py # CostEstimate gains EUR fields via FxRate + fx.py # NEW: USD→EUR conversion (config/env/snapshot; never invent FX) + usage.py # NEW: UsageEvent + UsageLedger (append-only JSONL) + openrouter.py # default model → moonshotai/kimi-k3 after T01 smoke + config.py # LLMConfig.model default aligned + cli.py # run | cost report | spend week +``` + +### Defaults (after smoke gate) + +| Surface | New default | +|---|---| +| `OpenRouterAdapter` `_DEFAULT_MODEL` | `moonshotai/kimi-k3` | +| `LLMConfig.model` | `moonshotai/kimi-k3` | +| `create_adapter("openrouter")` with no model | inherits adapter default | +| Explicit profiles / activity-core triage model | **unchanged** (already pin a model) | + +If T01 smoke **fails** (auth, model unavailable, empty content), keep Claude Sonnet +as default, ship Kimi as an **opt-in** model id in rates + docs, and mark T02 +`wait` with a short note — do not force a broken default. + +### Cost + EUR + +- Keep rate table denominated in **USD** (OpenRouter list prices). +- Add `FxRate(usd_per_eur or eur_per_usd, source, captured_at)` with resolution: + 1. Explicit arg / config + 2. Env `LLM_CONNECT_EUR_PER_USD` (float: euros per one USD) + 3. Bundled snapshot rate with `captured_at` (operator can refresh) +- Extend `CostEstimate` with optional `cost_eur`, `prompt_cost_eur`, + `completion_cost_eur`, `fx_source` — missing FX yields `cost_eur=None` with + explicit source (same philosophy as unknown models). +- Never treat missing rates or missing FX as zero cost. + +### Usage ledger (spend history) + +Append-only JSONL, separate from `QualityLedger` (quality is adaptive-routing +signal; spend is operator accounting). + +```python +@dataclass(frozen=True) +class UsageEvent: + recorded_at: datetime # timezone-aware UTC + provider: str + model_id: str + prompt_tokens: int + completion_tokens: int + total_tokens: int + cost_usd: float | None + cost_eur: float | None + cost_source: str + fx_source: str | None + source: str # "cli" | "server" | "library" + tags: dict[str, Any] # optional request id, profile, etc. +``` + +Default ledger path resolution: + +1. `--ledger` / explicit path +2. `LLM_CONNECT_USAGE_LEDGER` env +3. `~/.local/share/llm-connect/usage.jsonl` (XDG-style fallback) + +Week windows use **Monday 00:00 local** → exclusive end. Local timezone default +`Europe/Berlin` (override: `--tz` / `LLM_CONNECT_TZ`). + +### CLI surface + +```bash +# One-shot prompt (records usage when ledger available) +llm-connect run "Summarise X" [--provider openrouter] [--model moonshotai/kimi-k3] +# stderr or footer: tokens in/out/total · € cost (and USD if useful) + +# Weekly spend +llm-connect spend week # current week Mon → now +llm-connect spend week --last # previous Mon → Sun +llm-connect spend week --json + +# Optional: show cost for a hypothetical token count +llm-connect cost estimate --model moonshotai/kimi-k3 --prompt-tokens N --completion-tokens M +``` + +Per-call reporting for `run`: always print tokens; print € when FX + rate known; +print clear `unknown` when not. + +## Scope guardrails + +**In scope** + +- Kimi K3 rate entry + default flip after smoke +- EUR conversion on top of existing USD rate table +- Usage ledger + CLI run/spend/cost commands +- Unit tests with fixed FX and fake clock; no live network in default CI +- Contract doc updates under `contracts/functional/` + +**Out of scope** + +- Multi-currency rate tables beyond USD list + EUR display +- Billing reconciliation against OpenRouter invoices (estimate-based only) +- Changing activity-core K8s model pins or triage profile model ids +- Full TUI / web dashboard +- Automatic live FX fetch in CI (optional later; snapshot + env is enough) + +## Tasks + +```task +id: LLM-WP-0007-T01 +status: todo +priority: high +``` + +**Validate Kimi K3 via OpenRouter.** Confirm `moonshotai/kimi-k3` is callable +with a real key when available (manual/operator smoke; CI uses mock). Add rate +table entry: prompt `$0.003` / completion `$0.015` per 1k USD (captured from +OpenRouter 2026-08-03). Document model id and pricing source in rates contract +or README providers table. Gate for T02. + +```task +id: LLM-WP-0007-T02 +status: todo +priority: high +``` + +**Promote Kimi K3 to OpenRouter basemodel default** after T01 smoke passes. +Update `openrouter._DEFAULT_MODEL`, `LLMConfig.model`, any factory/docs that +advertise the default, and tests that hard-code `anthropic/claude-sonnet-4` as +the implicit default. Leave explicit profile models untouched. If smoke failed, +leave default unchanged and record the blocker in the task notes. + +```task +id: LLM-WP-0007-T03 +status: todo +priority: high +``` + +**USD→EUR cost conversion.** Add `fx` helper + extend `CostEstimate` with EUR +fields and `fx_source`. Resolution order: explicit → env → bundled snapshot. +Update `contracts/functional/costs.md`. Tests: known FX, missing FX, unknown +model still yields `cost_usd=None` / `cost_eur=None`. + +```task +id: LLM-WP-0007-T04 +status: todo +priority: high +``` + +**Usage ledger.** Implement `UsageEvent` + append-only JSONL `UsageLedger` +(path locks patterned after `QualityLedger`). Helpers: `record(...)`, +`iter_events()`, `sum_range(start, end)` aggregating tokens + cost_usd/eur. +Default path via env / XDG. Tests for append, concurrent-safe append (best +effort flock), and range aggregation. + +```task +id: LLM-WP-0007-T05 +status: todo +priority: high +``` + +**CLI `run` with per-call cost footer.** Extend `llm-connect` with a `run` +subcommand that creates an adapter, executes a prompt, prints content, and +reports tokens + € (and USD). Record a `UsageEvent` to the ledger. Wire cost +estimation from response usage + rate table + FX. Tests with mock adapter. + +```task +id: LLM-WP-0007-T06 +status: todo +priority: high +``` + +**CLI weekly spend views.** `llm-connect spend week` and `spend week --last` +using Mon–Sun / Mon–now windows in `Europe/Berlin` (overridable). Human table + +`--json`. Empty ledger → zero totals, exit 0. Tests with synthetic events and +fixed timezone. + +```task +id: LLM-WP-0007-T07 +status: todo +priority: medium +``` + +**Optional library/server recording.** When `LLM_CONNECT_USAGE_LEDGER` is set, +record usage from adapter post-call path and/or server `/execute` (source +`library` / `server`) so non-CLI traffic contributes to weekly spend. Keep +opt-in so library consumers are not surprised by home-dir writes. + +```task +id: LLM-WP-0007-T08 +status: todo +priority: medium +``` + +**Docs + acceptance polish.** README: new default model, CLI examples for +`run` / `spend week`, env vars (`LLM_CONNECT_USAGE_LEDGER`, +`LLM_CONNECT_EUR_PER_USD`, `LLM_CONNECT_TZ`). Update `contracts/functional/` +as needed. Smoke script or Makefile target for optional live Kimi check. + +## Acceptance + +- [ ] `moonshotai/kimi-k3` is in the default rate registry with documented USD rates +- [ ] After successful smoke, OpenRouter/library default model is `moonshotai/kimi-k3` +- [ ] `llm-connect run "..."` prints token counts and € cost (or explicit unknown) +- [ ] Successful CLI runs append to the usage ledger +- [ ] `llm-connect spend week` and `spend week --last` show tokens + € for the + correct Mon-based windows +- [ ] Default CI tests pass offline (mock adapter, fixed FX, no live OpenRouter) +- [ ] Missing rate or FX never reported as €0.00 without an `unknown` marker + +## Risks / notes + +- **Model id spelling:** operator said "kimi-k3"; OpenRouter id is + `moonshotai/kimi-k3` (not K2 variants). Stick to that id unless operator + renames the requirement. +- **FX staleness:** bundled EUR rate will drift; env override is the operator + escape hatch. Do not scrape FX APIs in the hot path for v1. +- **Default flip blast radius:** any consumer relying on implicit Sonnet will + change behaviour — document in README and release notes; profiles that pin + models are safe. +- **Estimate vs invoice:** ledger costs are list-price estimates, not OpenRouter + billed totals (discounts, cache reads, provider routing may differ). + +## Implementation order + +T01 → T02 (gated) in parallel with T03 → T04 → T05 → T06 → T07 → T08. +T03/T04 can start before T01; T02 must wait for T01 smoke outcome.