Round 3 of the dogfood confirmed that every multi-branch CLI command
works end-to-end, but the single biggest remaining friction is still
checkpoint payload construction. Each agent hands off a ~15-18 KB
markdown document; turning that into the Smriti schema (decisions,
assumptions, tasks, open_questions, entities, artifacts) is three
minutes of hand-written JSON per checkpoint and adds no product value.
This build adds an LLM-powered extractor that collapses that work into
one pipe command:
cat /tmp/r3_agent_a_output.md | smriti checkpoint create my-project \
--extract --author-agent codex-A
The CLI reads stdin as freeform markdown, calls the new
POST /api/v5/checkpoint/extract endpoint, maps the returned fields
into a commit payload, and writes the checkpoint. --dry-run prints the
extracted payload without committing so users can review first.
--extract and --from-json are mutually exclusive.
Backend architecture mirrors the existing review endpoint: stateless
LLM call (no session or commit ID required), uses the same background
intelligence provider (cfg.background.provider / cfg.background.model)
as draft and review, same JSON-mode prompt shape, same 502-on-parse-
error pattern. The extractor endpoint differs in one small way: it
passes allow_mock=True to get_adapter so unconfigured test envs fall
back to MockAdapter without raising 500. Production envs always have
a real provider configured and never hit this fallback.
The extractor is the first LLM-backed endpoint that gets tested
against a real mock response. To make that work, MockAdapter.send now
detects response_format={"type": "json_object"} in kwargs and returns
a canned JSON blob covering every field any current Smriti endpoint
looks for (title, objective, summary, decisions, assumptions, tasks,
open_questions, entities, artifacts, issues, suggestions). Existing
chat.send text-mode tests are unaffected because they don't pass
response_format. This also unblocks future tests for draft and review.
Manual verification against a real OpenAI provider: piped a realistic
23-line handoff markdown with 4 decisions, 3 assumptions, 3 tasks,
2 open questions, and a python code block. The extractor returned
exactly those items in the right fields (4/3/3/2/1) and produced a
valid checkpoint with all fields populated. Round 4's load-bearing
claim — zero hand-written JSON per checkpoint — is now achievable.
153/153 backend tests pass (149 pre-existing + 4 new extract tests).
6.4 KiB
smriti-cli
Command-line access to Smriti's reasoning-state backend. Built for coding agents and scripts — pipe JSON in, get readable markdown out.
Install
From the repo root:
cd cli
pip install -e .
This installs a smriti command on your PATH.
Configuration
Set the backend URL via env var (defaults to http://localhost:8000):
export SMRITI_API_URL=http://localhost:8000
Or pass --api-url on any command.
Commands
smriti space list
smriti space create <name> [--description "..."]
smriti space delete <space> [-y]
smriti state <space> # continuation brief (full artifacts by default)
smriti state <space> --preview # truncate artifacts to a short preview
smriti state <space> --json # structured output
smriti fork <checkpoint-id> [--branch <name>] # new session from checkpoint
smriti restore <checkpoint-id> # brief of a specific checkpoint
smriti compare <checkpoint-a> <checkpoint-b> # structured diff
smriti checkpoint create <space> # reads JSON from stdin
smriti checkpoint create <space> --from-json <path> # from JSON file
smriti checkpoint create <space> --extract # reads markdown, LLM extracts schema fields
smriti checkpoint create <space> --extract --dry-run # preview the extracted payload without committing
smriti checkpoint create <space> --session <session-id> # attach to existing session
smriti checkpoint create <space> --author-agent claude-code
smriti checkpoint create <space> --project-root /path # override cwd auto-capture
smriti checkpoint show <checkpoint-id>
smriti checkpoint list <space>
smriti checkpoint review <checkpoint-id>
smriti checkpoint delete <checkpoint-id> [--cascade] [-y]
smriti state shows full artifact content by default — flip to --preview for the truncated brief.
smriti checkpoint create auto-captures the current working directory as the checkpoint's project_root so cross-agent handoffs know where the project actually lives on disk. Pass --project-root /absolute/path to override or --no-project-root to skip. Tag the checkpoint with an explicit --author-agent <name> (like claude-code or codex-local); without it, the backend falls back to the session's active provider.
Extracting checkpoints from freeform agent output: instead of hand-writing the JSON payload, pipe an agent's markdown output to --extract and let Smriti's background LLM extract the structured fields (decisions, assumptions, tasks, open questions, entities, artifacts) for you:
# Extract and commit in one step
cat /tmp/r3_agent_a_output.md | smriti checkpoint create my-project --extract --author-agent codex-A
# Preview what would be extracted, without committing
cat /tmp/r3_agent_a_output.md | smriti checkpoint create my-project --extract --dry-run
--extract reads stdin as freeform markdown, sends it to POST /api/v5/checkpoint/extract, and uses the returned fields to build the commit payload. --dry-run prints the extracted payload as JSON and exits without creating a checkpoint. --extract and --from-json are mutually exclusive.
Every command supports --json for structured output.
Typical agent workflow
Read current project state:
smriti state my-project
Write a checkpoint from a JSON object piped on stdin:
cat <<'JSON' | smriti checkpoint create my-project
{
"message": "Decided to use Pydantic for state validation",
"objective": "Build runtime-enforced state layer",
"summary": "...",
"decisions": ["Use Pydantic BaseModel for state", "extra=forbid blocks injection"],
"assumptions": ["Latency cost is acceptable"],
"tasks": ["Benchmark validation overhead"],
"open_questions": ["How to handle shared state across agents"],
"entities": ["Pydantic", "BaseModel"],
"artifacts": [
{"id": "a1", "type": "text", "label": "Draft implementation", "content": "..."}
]
}
JSON
Review a specific checkpoint for consistency issues:
smriti checkpoint review <checkpoint-id>
Multi-branch workflow
When you want to explore an alternative direction from a checkpoint without losing the main branch, fork it into a new session and write checkpoints there:
# Fork a new session off checkpoint C1
smriti fork <C1-checkpoint-id> --branch experiment
# The output gives you the new session ID. Write a checkpoint to that session:
cat <<'JSON' | smriti checkpoint create my-project --session <fork-session-id>
{
"message": "Alternative design direction",
"summary": "...",
"decisions": ["Try stdlib only instead of click"]
}
JSON
# Compare the two branches
smriti compare <C1-checkpoint-id> <new-checkpoint-id>
# Read any checkpoint as a continuation brief (what you'd need to continue from it)
smriti restore <new-checkpoint-id>
smriti compare shows a structured diff with Shared, Only in A, and Only in B sections for decisions, assumptions, and tasks, plus the lowest common ancestor of the two checkpoints. The shared-set matching is case- and punctuation-insensitive, so two agents phrasing the same commitment differently still show up as shared.
Checkpoint payload schema
Only message is required. Every other field defaults to empty.
| Field | Type | Notes |
|---|---|---|
message |
string | Short title (required) |
objective |
string | What you are working toward |
summary |
string | Narrative of what was figured out |
decisions |
string[] | Explicit choices made |
assumptions |
string[] | Things taken for granted |
tasks |
string[] | Concrete action items |
open_questions |
string[] | Unresolved issues |
entities |
string[] | Key concepts, tools, names |
artifacts |
object[] | {id, type, label, content} entries |
project_root |
string | Absolute path to the project's working directory. Auto-captured by the CLI at commit time; can be overridden via the payload or --project-root. |
author_agent |
string | Agent identifier. The CLI flag --author-agent overrides any payload value; when unset the backend tags the checkpoint with the session's active provider. |
Space resolution
<space> arguments accept either the space name or the UUID. Names are matched exactly first, then case-insensitively. If multiple spaces match, the CLI asks you to use a UUID.
Exit codes
0success1API error, invalid input, or backend unreachable130interrupted (Ctrl+C)