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

12 KiB
Raw Blame History

id type title domain repo status owner topic_slug planning_priority planning_order created updated depends_on_workplans related_workplans state_hub_workstream_id
LLM-WP-0007 workplan Kimi K3 default via OpenRouter + EUR spend reporting agents llm-connect finished codex kimi-k3-default-spend-reporting high 7 2026-08-03 2026-08-03
LLM-WP-0005
LLM-WP-0006
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_costCostEstimate with cost_usd only (costs.py)
CLI (llm-connect) Registry tools only: rates show, classes show, classes fitno 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).

@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

# 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

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.

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.

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.

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.

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.

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.

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.

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

  • 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.

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.