llm-connect/workplans/LLM-WP-0007-kimi-k3-default-and-spend-reporting.md
codex 8bb446c1fa
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
fix(workplans): adopt ADR-007 derived identifiers for unregistered records
These workplans exist only in the retired local hub. Their random pre-ADR-007
identifiers are refused by C-06 as stale references, so they cannot be
registered. Deriving from the canonical record id takes no identity from
anything: central does not hold them and the old ids die with the cache.

Records central already holds were deliberately left untouched.

Refs CUST-WP-0068-T06

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 2583210@bnt-lap001
Assistant-Session: f2bff2d5-e9b2-4338-92ca-10282a927006
2026-08-25 20:13:58 +02:00

309 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
id: LLM-WP-0007
type: workplan
title: "Kimi K3 default via OpenRouter + EUR spend reporting"
domain: agents
repo: llm-connect
status: finished
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
state_hub_workstream_id: "7878d7d1-b56f-5ac7-9203-e2ce44ac7c2e"
---
# LLM-WP-0007 — Kimi K3 default via OpenRouter + EUR spend reporting
**status:** finished
**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 monsun / monnow 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: done
priority: high
state_hub_task_id: "ca5aabf6-6444-5710-8437-241c99177152"
```
**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: done
priority: high
state_hub_task_id: "cb0a4fb2-d008-51f8-b222-ee16dfdf16fe"
```
**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: done
priority: high
state_hub_task_id: "6fa5a4d7-e694-5a7b-b16b-4652062dd78d"
```
**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: done
priority: high
state_hub_task_id: "0ac06271-5dea-5f4f-8ce6-b5db549a21df"
```
**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: done
priority: high
state_hub_task_id: "33443864-fdce-5b24-8a7f-00c0697a69a1"
```
**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: done
priority: high
state_hub_task_id: "23223ec1-5575-589d-a8b0-50df8d38bd3c"
```
**CLI weekly spend views.** `llm-connect spend week` and `spend week --last`
using MonSun / Monnow 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: done
priority: medium
state_hub_task_id: "79dd15d9-b2ad-547b-a4e8-d80b26d342e4"
```
**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: done
priority: medium
state_hub_task_id: "4b835a23-048b-51ee-837d-48abd108f182"
```
**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
- [x] `moonshotai/kimi-k3` is in the default rate registry with documented USD rates
- [x] After successful smoke, OpenRouter/library default model is `moonshotai/kimi-k3`
- [x] `llm-connect run "..."` prints token counts and € cost (or explicit unknown)
- [x] Successful CLI runs append to the usage ledger
- [x] `llm-connect spend week` and `spend week --last` show tokens + € for the
correct Mon-based windows
- [x] Default CI tests pass offline (mock adapter, fixed FX, no live OpenRouter)
- [x] 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.
## Implementation notes (2026-08-03)
- Live OpenRouter smoke: `moonshotai/kimi-k3` returned content "ok" with usage tokens (Together provider). Note: short `max_tokens` can yield empty `content` while spending tokens on reasoning.
- Default OpenRouter / LLMConfig model flipped to `moonshotai/kimi-k3`.
- Added `fx.py`, EUR fields on `CostEstimate`, `usage.py` ledger, CLI `run` / `cost estimate` / `spend week`.
- Opt-in library/server recording via `LLM_CONNECT_USAGE_LEDGER`; CLI suppresses double-record.
- Full unit suite: 224 passed offline.