A fresh Smriti install with no provider configured could still run
`smriti checkpoint create --extract`, which silently returned
MockAdapter content like "Mock decision from provider". If committed,
that placeholder text became part of the user's real reasoning state.
Root cause: backend/app/api/routes/checkpoint.py:417 called
`get_adapter(cfg.background.provider, allow_mock=True)`. The
`allow_mock=True` flag means the registry quietly returns MockAdapter
when no API key is configured, rather than raising. The CLI received
the canned mock JSON and committed it without inspecting whether it
came from a real LLM.
The extract endpoint was the only route in the codebase with this
pattern — draft, review, chat title, and chat send already correctly
pass `allow_mock=False`.
The new contract:
- Core Smriti (setup, doctor, quickstart, state/current/metrics,
claims, attach, manual JSON checkpoints) requires no API key.
- Real LLM-backed paths (`--extract`, draft, review, chat send)
require a configured provider — OpenAI / Anthropic / OpenRouter /
generic OpenAI-compatible (local models like Ollama).
- Mock extraction still works for tests and demos, but only when the
caller explicitly opts in (use_mock=true on the HTTP payload).
It is never silently the default.
Backend:
- POST /api/v5/checkpoint/extract now passes allow_mock=False and
catches ProviderNotConfiguredError, returning HTTP 412 with a
structured detail: error code, human message, the provider it
tried, and a list of fix paths the CLI surfaces.
- CheckpointExtractResponse gains `provider` and `model` echo fields
(additive, default empty) so callers can confirm what answered.
CLI:
- checkpoint create --extract catches 412 and prints the actionable
fix list; exits 78 (EX_CONFIG). Defense in depth: even on a 200,
refuses to commit if response.provider == "mock" on the default
path (so any future regression in the backend is still caught).
- smriti doctor surfaces background provider state prominently:
`ready (real LLM extraction enabled)` or `⚠ MOCK or DISABLED — …
will fail until a provider is configured`.
- smriti doctor --strict exits 78 when the background provider is
mock/disabled or the backend is unreachable. Safe to wire into
CI before any --extract step.
- On a successful --extract commit, the CLI shows `extracted via
<provider>/<model>` under the commit confirmation.
Docs:
- README: new "Provider configuration (LLM-backed features)" section
drawing the boundary explicitly; mentions the generic provider for
local OpenAI-compatible models; flags mock as test-only.
- .env.example: rewrote the provider section so an empty key or a
model-without-a-key is not interpreted as "ready".
- Skill pack template: new §4.1 "Before your first --extract: verify
the provider" telling agents to run `smriti doctor`, refuse
--extract when background_provider is mock/disabled, and fall back
to manual JSON checkpoints or ask the human to configure a
provider. Re-rendered to AGENTS.md (Codex target). The Claude Code
target (.claude/skills/smriti/SKILL.md) is gitignored per-user
install; rerun `smriti skills install claude-code` to refresh.
- website/index.html: Try-it lede now spells out which features need
a provider rather than gesturing at "optional LLM features".
Tests:
- test_extract_without_provider_fails_loud: regression for the bug —
monkeypatches get_adapter to raise ProviderNotConfiguredError,
asserts HTTP 412 with the structured detail shape, and asserts
the response body contains neither "Mock decision from provider"
nor "Mock Checkpoint". This test would fail on pre-fix code.
- test_extract_with_provider_echoes_provider_and_model: pins the
green path — provider and model must be echoed and must not be
"mock" when the real adapter answers.
- test_extract_happy_path_with_mock: unchanged, still pins the
explicit use_mock=true contract.
- Full backend integration suite: 165 passed locally (with the
pre-existing real-provider draft test passing under
backend/config/providers.yaml).