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).
The repo-state section added by the previous commit shows working-tree
and git-upstream drift. It does not answer the question Smriti's trust
story actually needs: has the repo moved since the last checkpoint?
Record git provenance on checkpoints and compare against it. The CLI
captures the git HEAD and branch when it creates a checkpoint; the V4
commit endpoint stores that under the commit's context_blob and returns
it on read. `smriti state` then compares the working repo against the
latest checkpoint's recorded HEAD/branch and surfaces:
- repo unchanged since the last checkpoint
- repo is N commit(s) ahead — recorded state may be stale
- repo history has diverged from the last checkpoint
- the last checkpoint was taken on a different branch
All local and read-only: no fetch, no reconciliation. Checkpoints made
before this feature (or by the MCP server) record no git state and are
simply left uncompared.
`smriti current` already normalizes a list-valued blocked_by to a clean
comma-separated string via the backend CurrentTask validator, but `smriti
state` rendered it through an f-string and leaked the raw Python list repr
(['a', 'b']) into the state brief — the surface agents read at session start.
Add a `_coerce_blocked_by` helper in the CLI formatters mirroring that
validator, and apply it in `_normalize_task_item` so every `smriti state`
task line renders blocked_by the same way `smriti current` does. Adds
regression tests.
Tasks gain an optional id field (short slug like "impl-1", "docs-arch").
Claims gain an optional task_id field referencing a specific task. The
state brief shows (id: X) on tasks and (task: X) on claims, making it
precise which task a claim covers.
Skill pack v1.9 teaches the recheck pattern: after creating a claim,
re-read state to detect if another agent claimed the same task_id in
the race window. If collision detected, abandon and pivot.
This solves the near-simultaneous start problem from the autonomy
validation where both agents picked [docs] because claims had no
task-level identity.
Tasks in checkpoints evolve from flat strings to objects with optional
intent_hint (implement/review/investigate/docs/test), blocked_by
(dependency label), and status (open/done). Agents reading the state
brief can now self-select complementary work by matching task intents
against active claim intent_types — no founder routing needed.
Backward-compatible: old string tasks normalize at render time. No
schema migration. JSONB handles both shapes. Skill pack v1.7 teaches
the autonomous selection reflex.
smriti state and smriti_state now default to the /state endpoint from
the previous commit, which returns main HEAD plus active non-main
branches plus a lightweight divergence signal. The main continuation
brief still renders first and is unchanged; the two new sections are
appended after it and elided cleanly when there is no fork activity.
Output shape for a single-agent project is byte-identical to before,
so existing users see no change. Projects with multiple agents on
different branches now see one line per active branch in an Active
branches section, and if any branch disagrees with main on decisions
a Divergence signal section names the specific conflicting decisions
and points at smriti compare for the full diff.
Hard caps from the endpoint (5 branches, 2 divergent pairs, 3
decisions per side) keep the aggregate output digestible no matter
how busy the project is.
--main-only (CLI) / main_only=True (MCP) falls back to the legacy
two-call get_head + get_commit path for scripts that parsed the old
shape.
format_state_brief gains an optional space_state kwarg; existing
callers passing only positional args are unaffected.
Round 5 dogfood surfaced three small friction items:
- smriti_list_checkpoints only rendered short hashes, forcing agents to
make a second round trip to get the UUID they needed for fork/compare/
restore. format_commit_list now appends the full UUID in parentheses
when c["id"] is populated; legacy callers without ids still render a
clean line. CLI output benefits equally since formatters are shared.
- The mcp SDK logs "Processing request of type ..." at INFO on every
tool call, cluttering host log panels. smriti-mcp main() now defaults
the mcp logger to WARNING. Set SMRITI_MCP_LOG_LEVEL=INFO (or DEBUG)
in the host's env block to re-enable verbose logging when debugging.
- Add README notes acknowledging that the mcp SDK negotiates the
protocol version on its own during initialize, and documenting the
new log-level env var.
Cross-agent handoffs need to know two things the old schema did not
carry: where the project lives on disk (so the receiving agent opens
the right repo), and which agent wrote the checkpoint (so branches
can be attributed when two agents have forked the same tree). Round 1
of the dogfood lost the first one — Agent A designed files at one
path and Agent B wrote them at a different path because nothing in
the schema carried "where we are". Round 2 worked around it by putting
the path in the prompt, but the schema still had no slot for it.
This build adds a `project_root` column to the commits table (nullable
TEXT, no backfill), plumbs it through the V2 commit-create path and
V4 chat-commit path, and has `smriti checkpoint create` auto-capture
the current working directory by default. `--project-root /path`
overrides and `--no-project-root` opts out.
`author_agent` already existed on the model but the V4 chat-commit
endpoint hardcoded it to the session's active provider (e.g.
"anthropic"), so it was a provider name, not an agent identifier.
This build makes the request's `author_agent` field win when provided
and falls back to the session provider only when it isn't. CLI adds
`--author-agent <name>` so agents can tag themselves cleanly as
"claude-code" or "codex-local" rather than the underlying provider.
Both fields surface in `smriti state`, `smriti checkpoint show`, and
`smriti restore` meta lines — `by <agent>` and `at <path>` (with $HOME
tilde-expanded for readability). The meta line elides each segment
cleanly when the field is null, so old checkpoints without either
value still render correctly.
While I was here, flipped `smriti state` to show full artifacts by
default. `--preview` is the new way to get truncated previews.
`--full-artifacts` is kept as a no-op alias so existing scripts that
explicitly passed it still work. The CLI is agent-first and agents
want the full content for handoff; humans wanting a quick glance can
pass `--preview`.
149/149 backend tests pass (147 pre-existing + 2 new: round-trip of
the two fields and the author_agent fallback regression).
Round 2 of the agent handoff dogfood showed that every multi-branch
operation required reaching past the CLI into curl: fork had no CLI
command, `smriti checkpoint create` always spawned a fresh session with
no way to attach to a forked one, and the compare endpoint returned
useless output (common_ancestor_commit_id was missing from the response,
and shared-set matching was exact-string so two agents phrasing the
same commitment differently showed zero overlap).
This ships the full CLI surface for multi-branch workflows plus the
backend fixes that make compare actually useful:
smriti fork <checkpoint-id> [--branch <name>]
smriti restore <checkpoint-id>
smriti compare <checkpoint-a> <checkpoint-b>
smriti checkpoint create <space> --session <session-id>
The compare endpoint now walks parent chains to compute a lowest
common ancestor (bounded to 1000 steps with a cycle guard) and returns
it on CheckpointDiff as an optional uuid. Shared-set matching uses a
lightweight lowercase + punctuation-strip + whitespace-collapse
normalization for keying, but returns the original A-side strings so
the output stays readable. Four new compare tests cover direct and
two-step LCA, null LCA for unrelated checkpoints, and normalized
shared-set matching. Existing compare tests still pass unchanged
because their data ("Use Redis" vs "Use Postgres") is distinct at any
sensible normalization level.
`smriti restore <checkpoint>` is a pure read — it renders any
checkpoint as a continuation brief matching `smriti state <space>`
shape. `smriti fork` derives the space from the checkpoint so the
user does not have to pass it separately. `--session` on checkpoint
create is purely additive: when absent, the existing auto-session
behavior is unchanged.
147/147 backend tests pass (143 pre-existing + 4 new).
Introduce a thin Python CLI that wraps the backend REST API. Seven
commands: space list, space create, state, checkpoint create,
checkpoint show, checkpoint list, checkpoint review. Reads piped
JSON on stdin for checkpoint create, prints a continuation-oriented
markdown brief for state. Supports --json on every command for
structured output.
Fixes a V2 schema drift where the commit response omitted
assumptions and artifacts, so the CLI can read full checkpoints
via the cleaner V2 single-resource endpoints. Updates README,
ARCHITECTURE, and DECISIONS to frame Smriti as a reasoning-state
backend with the chat UI and CLI as two clients of the same core.