loop-memory 0.4.1__tar.gz → 0.4.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {loop_memory-0.4.1 → loop_memory-0.4.2}/PKG-INFO +166 -33
- {loop_memory-0.4.1 → loop_memory-0.4.2}/README.md +165 -32
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory.egg-info/PKG-INFO +166 -33
- {loop_memory-0.4.1 → loop_memory-0.4.2}/pyproject.toml +1 -1
- {loop_memory-0.4.1 → loop_memory-0.4.2}/tests/test_repo_framing.py +89 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/LICENSE +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/__init__.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/backends/__init__.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/backends/embedding.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/backends/sentence_embedder.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/backends/vector_store.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/cli/__init__.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/cli/_common.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/cli/commands/__init__.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/cli/commands/cognitive.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/cli/commands/diag.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/cli/commands/graph.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/cli/commands/hooks.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/cli/commands/read.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/cli/commands/serve.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/cli/commands/write.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/cli/main.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/engine/__init__.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/engine/loop.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/engine/reflect.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/examples/__init__.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/examples/demo.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/export/__init__.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/export/memory_md.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/graph/__init__.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/graph/build.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/graph/extract.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/ingest/__init__.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/ingest/loader.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/ingest/pipeline.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/jobs/__init__.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/jobs/cognitive.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/jobs/compact.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/jobs/consolidate.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/jobs/contradiction.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/jobs/evolution.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/jobs/graph.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/jobs/llm_compact_pass.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/jobs/llm_consolidate.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/jobs/scheduler.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/llm/__init__.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/llm/base.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/llm/openai_adapter.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/llm/providers.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/mcp/__init__.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/memory/__init__.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/memory/types.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/privacy/__init__.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/privacy/private.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/privacy/redact.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/py.typed +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/sdk.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/sdk_extensions.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/security/__init__.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/security/secrets.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/serve/__init__.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/serve/app.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/serve/handlers.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/serve/routes/_shared.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/serve/routes/admin.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/serve/routes/cognitive.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/serve/routes/export.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/serve/routes/graph.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/serve/routes/insights.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/serve/routes/memories.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/serve/routes/sessions.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/serve/routes/system.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/serve/routes/wiki.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/serve/static/__init__.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/serve/static/index.html +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/serve/watcher.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/storage/__init__.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/storage/retrieval.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/storage/sqlite_store.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/wiki/__init__.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/wiki/backfill.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/wiki/classifier.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/wiki/prompts.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory/wiki/scope.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory.egg-info/SOURCES.txt +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory.egg-info/dependency_links.txt +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory.egg-info/entry_points.txt +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory.egg-info/requires.txt +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/loop_memory.egg-info/top_level.txt +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/setup.cfg +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/tests/test_admin_ingest_route.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/tests/test_agent_memory_api.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/tests/test_agent_memory_sdk.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/tests/test_auth_token_rotate.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/tests/test_cli_v7.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/tests/test_cli_version.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/tests/test_contradictions.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/tests/test_evolution.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/tests/test_evolution_quality.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/tests/test_expanduser.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/tests/test_export_ask.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/tests/test_graph.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/tests/test_ingest.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/tests/test_llm_consolidator.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/tests/test_llm_fingerprint_not_persisted.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/tests/test_llm_providers.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/tests/test_llm_test_endpoint.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/tests/test_loop.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/tests/test_mcp.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/tests/test_memories_pagination.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/tests/test_openclaw_loader.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/tests/test_reflection.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/tests/test_score_api.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/tests/test_scoring_v2.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/tests/test_secrets.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/tests/test_serve_app.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/tests/test_serve_handlers.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/tests/test_session_order.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/tests/test_store.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/tests/test_summarization.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/tests/test_universal_memory.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/tests/test_vector_store.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/tests/test_watcher.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/tests/test_wiki_classifier.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/tests/test_wiki_export_escape.py +0 -0
- {loop_memory-0.4.1 → loop_memory-0.4.2}/tests/test_wiki_prompts.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: loop-memory
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.2
|
|
4
4
|
Summary: A general-purpose, local memory system for every AI agent you run. Loop Memory auto-captures conversations from Codex / Claude / Hermes / OpenClaw, scores them by importance × recency × usage × feedback, distils them into a curated wiki, and serves everything from a single web UI.
|
|
5
5
|
Author: Loop Memory contributors <loop-memory@users.noreply.github.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -66,6 +66,40 @@ Dynamic: license-file
|
|
|
66
66
|
|
|
67
67
|
---
|
|
68
68
|
|
|
69
|
+
> **What's new in 0.4.1** — CLI dispatch hardened so every subcommand's
|
|
70
|
+
> `--help` / `--version` exits 0 cleanly (no more `ValueError: unknown
|
|
71
|
+
> source: '--help'` on a zero-deps install). Added a static
|
|
72
|
+
> `COMMAND_HELP` table guarded by 10 new regression cases in
|
|
73
|
+
> `tests/test_cli_version.py`, plus GitHub `About` / README lead now
|
|
74
|
+
> surface for "agent loop" searches.
|
|
75
|
+
> [Full changelog →](CHANGELOG.md)
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Table of contents
|
|
80
|
+
|
|
81
|
+
- [What it does](#what-it-does)
|
|
82
|
+
- [Install](#install)
|
|
83
|
+
- [Quickstart](#quickstart)
|
|
84
|
+
- [Why Loop Memory vs. every other agent-memory project](#why-loop-memory-vs-every-other-agent-memory-project)
|
|
85
|
+
- [Architecture & docs](#architecture--docs)
|
|
86
|
+
- [After install: 30-second setup](#after-install-30-second-setup)
|
|
87
|
+
- [Auto-capture (after every conversation)](#auto-capture-after-every-conversation)
|
|
88
|
+
- [Dashboard + Evolution consolidator](#dashboard--evolution-consolidator-看板--进化式蒸馏)
|
|
89
|
+
- [Auto-feedback into every LLM client](#auto-feedback-into-every-llm-client-反哺)
|
|
90
|
+
- [Web UI](#web-ui)
|
|
91
|
+
- [Programmatic use](#programmatic-use)
|
|
92
|
+
- [The four-stage loop](#the-four-stage-loop)
|
|
93
|
+
- [Project layout](#project-layout)
|
|
94
|
+
- [Security & auth token](#security--auth-token)
|
|
95
|
+
- [Wiki scope auto-classification](#wiki-scope-auto-classification)
|
|
96
|
+
- [FAQ & troubleshooting](#faq--troubleshooting)
|
|
97
|
+
- [Run the tests](#run-the-tests)
|
|
98
|
+
- [Using distilled knowledge in your clients](#using-distilled-knowledge-in-your-clients)
|
|
99
|
+
- [License](#license)
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
69
103
|
## What it does
|
|
70
104
|
|
|
71
105
|
**Loop Memory** gives every agent you use a single, persistent brain
|
|
@@ -108,6 +142,21 @@ via MCP / hooks]
|
|
|
108
142
|
|
|
109
143
|
*One loop, many agents, one evolving wiki.*
|
|
110
144
|
|
|
145
|
+
**Supported agents:**
|
|
146
|
+
|
|
147
|
+
| Agent | Capture path | Hook shipped? | Notes |
|
|
148
|
+
| -------------- | ----------------------------------- | :-----------: | ----- |
|
|
149
|
+
| Codex CLI | `~/.codex/sessions/*.json` | ✅ | MCP + SessionStart auto-wired by `install-hooks` |
|
|
150
|
+
| Claude Code | `~/.claude/**/*.jsonl` | ✅ | MCP + SessionStart auto-wired by `install-hooks` |
|
|
151
|
+
| Hermes | `~/.hermes/**/*.jsonl` | ✅ | MCP + SessionStart auto-wired by `install-hooks` |
|
|
152
|
+
| OpenClaw/clawx | `~/.openclaw/agents/main/sessions` + `workspace/memory/*.md` | ✅ | watcher only (no MCP yet); `openclaw-setup` enables launchd |
|
|
153
|
+
| Anything else | any on-disk transcript dir | — | use `loop-memory hook --source <name> --watch <dir>` (see [docs/auto-capture.md](docs/auto-capture.md)) |
|
|
154
|
+
|
|
155
|
+
The shipped hooks are the four popular agents we maintain in-tree.
|
|
156
|
+
The generic watcher CLI is the supported extension point for every
|
|
157
|
+
other agent — Aider, Cursor, Copilot, Cline, Continue, Goose, your
|
|
158
|
+
own home-grown CLI, anything that drops JSON/JSONL on disk.
|
|
159
|
+
|
|
111
160
|
---
|
|
112
161
|
|
|
113
162
|
## Install
|
|
@@ -229,6 +278,7 @@ flavors:
|
|
|
229
278
|
| Claude Code | `loop-memory hook --source claude --watch ~/.claude` |
|
|
230
279
|
| Hermes | `loop-memory hook --source hermes --watch ~/.hermes` |
|
|
231
280
|
| OpenClaw (clawx) | `loop-memory hook --source openclaw --watch ~/.openclaw/agents/main/sessions` — also ingests `workspace/memory/*.md` daily logs |
|
|
281
|
+
| Anything else (Aider, Cursor, Copilot, Cline, Continue, Goose, …) | `loop-memory hook --source <name> --watch <path/to/transcripts>` — see [docs/auto-capture.md](docs/auto-capture.md) for loader requirements |
|
|
232
282
|
|
|
233
283
|
Three of these in a `tmux` session, or persisted via launchd, keeps
|
|
234
284
|
your memory store fresh without any clicks. Run `loop-memory
|
|
@@ -335,6 +385,34 @@ User signals close the loop:
|
|
|
335
385
|
- Every `recall()` / search bumps `recall_count` on the returned
|
|
336
386
|
rows so the next Stage-1 ranks them higher.
|
|
337
387
|
|
|
388
|
+
### Cognitive sleep (v7)
|
|
389
|
+
|
|
390
|
+
`loop-memory cognitive-sleep [--apply]` runs an **auditable cleanup
|
|
391
|
+
pass** over the store:
|
|
392
|
+
|
|
393
|
+
- Surfaces contradictions between memories (e.g. "user prefers X"
|
|
394
|
+
vs. "user prefers Y") so you can resolve them in one click rather
|
|
395
|
+
than chasing them across sessions.
|
|
396
|
+
- Drops items that are below the configurable floor (`min_score`) and
|
|
397
|
+
older than the floor age.
|
|
398
|
+
- Merges near-duplicate memories (cosine ≥ 0.95 with `MergeableBy` rules).
|
|
399
|
+
- Emits a full audit row per action — visible in the Dashboard →
|
|
400
|
+
**Audit** tab and reachable via the MCP `audit` tool, so every
|
|
401
|
+
byte the consolidator ever touches is traceable.
|
|
402
|
+
|
|
403
|
+
Dry-run by default; pass `--apply` to commit.
|
|
404
|
+
|
|
405
|
+
### Knowledge graph
|
|
406
|
+
|
|
407
|
+
`loop-memory graph-rebuild` extracts entities from every distilled
|
|
408
|
+
wiki page and every long-term memory, then materialises a typed
|
|
409
|
+
relation graph:
|
|
410
|
+
|
|
411
|
+
- Visible as the **Knowledge graph** globe tab in the web UI.
|
|
412
|
+
- Queryable via the MCP `subgraph` and `remember_edge` tools.
|
|
413
|
+
- Re-built by the Evolution Consolidator's Stage-5 evolution memo, so
|
|
414
|
+
the graph evolves alongside the wiki.
|
|
415
|
+
|
|
338
416
|
## Auto-feedback into every LLM client (反哺)
|
|
339
417
|
|
|
340
418
|
Distilled knowledge is only useful if your LLM tools can actually
|
|
@@ -391,21 +469,6 @@ configuration, scheduling, language switching, and light/dark themes.
|
|
|
391
469
|
|
|
392
470
|
---
|
|
393
471
|
|
|
394
|
-
## Time-weighted scoring
|
|
395
|
-
|
|
396
|
-
Every memory carries a `score ∈ [0, 1]` recomputed from:
|
|
397
|
-
|
|
398
|
-
```
|
|
399
|
-
score = 0.35 · importance + 0.65 · recency
|
|
400
|
-
recency = ½ ^ (age / half_life)
|
|
401
|
-
```
|
|
402
|
-
|
|
403
|
-
`half_life` defaults to 30 days, configurable via
|
|
404
|
-
`consolidate(half_life_days=...)`. The UI shows the score as a
|
|
405
|
-
percentage; use `?min_score=0.85` to see only high-relevance memories.
|
|
406
|
-
|
|
407
|
-
---
|
|
408
|
-
|
|
409
472
|
## Programmatic use
|
|
410
473
|
|
|
411
474
|
```python
|
|
@@ -459,26 +522,35 @@ Even though v0.2 is built around local storage, the original
|
|
|
459
522
|
```
|
|
460
523
|
loop_memory/
|
|
461
524
|
loop_memory/
|
|
525
|
+
cli/main.py # CLI entrypoint + COMMAND_HELP table + `--version`
|
|
526
|
+
ingest/ # Codex / Claude / Hermes / OpenClaw / generic loaders
|
|
527
|
+
wiki/ # distillation, classifier, scope auto-promotion
|
|
528
|
+
graph/ # entity extraction + knowledge-graph build
|
|
529
|
+
jobs/ # consolidate / evolve / cognitive-sleep / scheduler / contradiction / graph
|
|
530
|
+
llm/ # provider protocol + OpenAI / Anthropic / Ollama / rule-based
|
|
531
|
+
backends/ # embedding (hashing / sentence-transformers) + vector store (memory / chroma)
|
|
532
|
+
storage/ # SQLite-backed MemoryStore + migrations
|
|
533
|
+
privacy/ # <private> stripping + regex redaction
|
|
534
|
+
security/ # Keychain / 0600-file secrets wrapper
|
|
535
|
+
mcp/ # stdio JSON-RPC MCP server
|
|
536
|
+
serve/ # FastAPI app, watcher, web UI (Timeline / Dashboard / Wiki / Graph)
|
|
537
|
+
export/ # markdown + v7 portable bundle export/import
|
|
538
|
+
sdk.py # four-verb stable API: remember / recall / forget / feedback
|
|
539
|
+
sdk_extensions.py # optional high-level helpers (graph edges, wiki pages)
|
|
540
|
+
engine/loop.py # Retrieve → Generate → Reflect → Store loop
|
|
462
541
|
memory/types.py # MemoryItem + 4 tiers
|
|
463
|
-
backends/embedding.py # BaseEmbedder, HashingEmbedder, IdentityEmbedder
|
|
464
|
-
backends/vector_store.py # VectorStore protocol + InMemory / Chroma
|
|
465
|
-
backends/sentence_embedder.py # optional sentence-transformers
|
|
466
|
-
llm/base.py # LLMClient protocol + EchoLLM + helpers
|
|
467
|
-
llm/openai_adapter.py # optional OpenAI client
|
|
468
|
-
engine/loop.py # the Retrieve → Generate → Reflect → Store loop
|
|
469
|
-
engine/reflect.py # reflection & summarization passes
|
|
470
|
-
storage/sqlite_store.py # persistent SQLite-backed MemoryStore
|
|
471
|
-
ingest/loader.py # CodexLoader, ClaudeLoader, HermesLoader
|
|
472
|
-
ingest/pipeline.py # session → MemoryStore
|
|
473
|
-
jobs/consolidate.py # background rescore + GC + dedupe
|
|
474
|
-
serve/app.py # FastAPI app for the local web UI
|
|
475
|
-
serve/static/index.html # the page
|
|
476
|
-
serve/watcher.py # filesystem watcher for auto-capture
|
|
477
|
-
cli/main.py # CLI entrypoint (chat / stats / ingest / consolidate / serve / hook)
|
|
478
542
|
examples/demo.py # runnable, zero-API-key demo
|
|
479
543
|
py.typed
|
|
480
|
-
tests/ #
|
|
481
|
-
docs/
|
|
544
|
+
tests/ # 475 unit tests across memory / SDK / serve / CLI / scripts
|
|
545
|
+
docs/
|
|
546
|
+
auto-capture.md # launchd / systemd / cron recipes
|
|
547
|
+
architecture.md # layered view + 5-stage evolution pipeline
|
|
548
|
+
api.md # HTTP API reference
|
|
549
|
+
agent-memory-api.md # four-verb SDK / HTTP / MCP contract
|
|
550
|
+
universal-agent-memory.md # v7 graph memory + cognitive sleep + bundles
|
|
551
|
+
providers.md # LLM provider reference
|
|
552
|
+
settings.md # settings table + secrets file
|
|
553
|
+
weekly-research-automation.md # how the project auto-evolves from upstream research
|
|
482
554
|
```
|
|
483
555
|
|
|
484
556
|
---
|
|
@@ -560,6 +632,67 @@ classifier is local and makes no model or network request on a wiki write.
|
|
|
560
632
|
|
|
561
633
|
---
|
|
562
634
|
|
|
635
|
+
## FAQ & troubleshooting
|
|
636
|
+
|
|
637
|
+
**Q: `pip install loop-memory` succeeds but `loop-memory serve` says `ModuleNotFoundError: No module named 'fastapi'`.**
|
|
638
|
+
A: `fastapi` is the optional `[serve]` extra. Install it explicitly:
|
|
639
|
+
`pip install 'loop-memory[serve]'` (or `'loop-memory[all]'` for everything).
|
|
640
|
+
|
|
641
|
+
**Q: My `~/.codex/sessions/` is empty / nothing appears in the UI.**
|
|
642
|
+
A: Run `loop-memory doctor` — it prints per-source paths, last-seen
|
|
643
|
+
mtime, and whether the watcher is running. Then check
|
|
644
|
+
`loop-memory hook --source codex --watch ~/.codex/sessions` is alive
|
|
645
|
+
in another shell (or via launchd — see [docs/auto-capture.md](docs/auto-capture.md)).
|
|
646
|
+
|
|
647
|
+
**Q: Distillation never finishes / wiki stays empty.**
|
|
648
|
+
A: You need an LLM provider configured. Open the web UI → ⚙ Model,
|
|
649
|
+
pick a provider, paste an API key, and click **Save**. Then either
|
|
650
|
+
wait for the scheduler or hit **� Run now**. Zero-deps installs ship
|
|
651
|
+
with a rule-based provider as a placeholder so the loop never blocks
|
|
652
|
+
on a missing key.
|
|
653
|
+
|
|
654
|
+
**Q: `loop-memory install-hooks` warns that the token file already exists.**
|
|
655
|
+
A: That's expected — it's idempotent. To force a rewrite, delete the
|
|
656
|
+
target files (`~/.codex/config.toml`, `~/.claude/mcp.json`,
|
|
657
|
+
`~/.hermes/mcp.json`) and re-run. The tool also refuses to touch
|
|
658
|
+
non-loop-memory config keys.
|
|
659
|
+
|
|
660
|
+
**Q: How big can the SQLite store get before I should worry?**
|
|
661
|
+
A: Practical floor: 100k memories / 10k wiki pages stays under ~80 MB
|
|
662
|
+
and `recall()` returns in <100 ms. The Evolution Consolidator is
|
|
663
|
+
designed to keep the wiki tight (~hundreds of pages) rather than let
|
|
664
|
+
it grow unbounded. Run `loop-memory cognitive-sleep --apply` weekly
|
|
665
|
+
to drop the long tail.
|
|
666
|
+
|
|
667
|
+
**Q: Can I sync the store across machines?**
|
|
668
|
+
A: The SQLite file is git-friendly and copy-friendly. The `MEMORY.md`
|
|
669
|
+
+ graph + memories + metadata **bundle** (`loop-memory export
|
|
670
|
+
<dir>`) is a portable v7 artefact you can commit, share, or
|
|
671
|
+
back-up. There is no first-class sync daemon — by design — so the
|
|
672
|
+
local-first guarantee is never violated.
|
|
673
|
+
|
|
674
|
+
**Q: Is there a hosted / cloud version?**
|
|
675
|
+
A: No. Loop Memory is MIT-licensed and 100% local; the SQLite file
|
|
676
|
+
lives under `~/.loop_memory/`. The web UI is bound to loopback by
|
|
677
|
+
default; binding to `0.0.0.0` prints a security warning and requires
|
|
678
|
+
an auth token.
|
|
679
|
+
|
|
680
|
+
**Q: Where do secrets / API keys live?**
|
|
681
|
+
A: Two places. Provider keys you set in the **⚙ Model** UI are
|
|
682
|
+
written to `~/.loop_memory/secrets.json` (mode 0600) via the
|
|
683
|
+
`loop_memory.security.secrets` wrapper, which prefers the macOS
|
|
684
|
+
Keychain on Darwin and falls back to the encrypted file on Linux.
|
|
685
|
+
The auth token used by the web UI is hashed in the SQLite settings
|
|
686
|
+
table — never stored in plaintext.
|
|
687
|
+
|
|
688
|
+
**Q: I see "no version" / "package not found" on the PyPI badge.**
|
|
689
|
+
A: shields.io pulls from a separate data source that lags PyPI by a
|
|
690
|
+
few minutes after a new release. Re-publish the badge warmer step
|
|
691
|
+
in `.github/workflows/publish.yml` to force a refresh, or wait ~30
|
|
692
|
+
minutes for shields.io to catch up.
|
|
693
|
+
|
|
694
|
+
---
|
|
695
|
+
|
|
563
696
|
## Run the tests
|
|
564
697
|
|
|
565
698
|
```bash
|
|
@@ -22,6 +22,40 @@
|
|
|
22
22
|
|
|
23
23
|
---
|
|
24
24
|
|
|
25
|
+
> **What's new in 0.4.1** — CLI dispatch hardened so every subcommand's
|
|
26
|
+
> `--help` / `--version` exits 0 cleanly (no more `ValueError: unknown
|
|
27
|
+
> source: '--help'` on a zero-deps install). Added a static
|
|
28
|
+
> `COMMAND_HELP` table guarded by 10 new regression cases in
|
|
29
|
+
> `tests/test_cli_version.py`, plus GitHub `About` / README lead now
|
|
30
|
+
> surface for "agent loop" searches.
|
|
31
|
+
> [Full changelog →](CHANGELOG.md)
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Table of contents
|
|
36
|
+
|
|
37
|
+
- [What it does](#what-it-does)
|
|
38
|
+
- [Install](#install)
|
|
39
|
+
- [Quickstart](#quickstart)
|
|
40
|
+
- [Why Loop Memory vs. every other agent-memory project](#why-loop-memory-vs-every-other-agent-memory-project)
|
|
41
|
+
- [Architecture & docs](#architecture--docs)
|
|
42
|
+
- [After install: 30-second setup](#after-install-30-second-setup)
|
|
43
|
+
- [Auto-capture (after every conversation)](#auto-capture-after-every-conversation)
|
|
44
|
+
- [Dashboard + Evolution consolidator](#dashboard--evolution-consolidator-看板--进化式蒸馏)
|
|
45
|
+
- [Auto-feedback into every LLM client](#auto-feedback-into-every-llm-client-反哺)
|
|
46
|
+
- [Web UI](#web-ui)
|
|
47
|
+
- [Programmatic use](#programmatic-use)
|
|
48
|
+
- [The four-stage loop](#the-four-stage-loop)
|
|
49
|
+
- [Project layout](#project-layout)
|
|
50
|
+
- [Security & auth token](#security--auth-token)
|
|
51
|
+
- [Wiki scope auto-classification](#wiki-scope-auto-classification)
|
|
52
|
+
- [FAQ & troubleshooting](#faq--troubleshooting)
|
|
53
|
+
- [Run the tests](#run-the-tests)
|
|
54
|
+
- [Using distilled knowledge in your clients](#using-distilled-knowledge-in-your-clients)
|
|
55
|
+
- [License](#license)
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
25
59
|
## What it does
|
|
26
60
|
|
|
27
61
|
**Loop Memory** gives every agent you use a single, persistent brain
|
|
@@ -64,6 +98,21 @@ via MCP / hooks]
|
|
|
64
98
|
|
|
65
99
|
*One loop, many agents, one evolving wiki.*
|
|
66
100
|
|
|
101
|
+
**Supported agents:**
|
|
102
|
+
|
|
103
|
+
| Agent | Capture path | Hook shipped? | Notes |
|
|
104
|
+
| -------------- | ----------------------------------- | :-----------: | ----- |
|
|
105
|
+
| Codex CLI | `~/.codex/sessions/*.json` | ✅ | MCP + SessionStart auto-wired by `install-hooks` |
|
|
106
|
+
| Claude Code | `~/.claude/**/*.jsonl` | ✅ | MCP + SessionStart auto-wired by `install-hooks` |
|
|
107
|
+
| Hermes | `~/.hermes/**/*.jsonl` | ✅ | MCP + SessionStart auto-wired by `install-hooks` |
|
|
108
|
+
| OpenClaw/clawx | `~/.openclaw/agents/main/sessions` + `workspace/memory/*.md` | ✅ | watcher only (no MCP yet); `openclaw-setup` enables launchd |
|
|
109
|
+
| Anything else | any on-disk transcript dir | — | use `loop-memory hook --source <name> --watch <dir>` (see [docs/auto-capture.md](docs/auto-capture.md)) |
|
|
110
|
+
|
|
111
|
+
The shipped hooks are the four popular agents we maintain in-tree.
|
|
112
|
+
The generic watcher CLI is the supported extension point for every
|
|
113
|
+
other agent — Aider, Cursor, Copilot, Cline, Continue, Goose, your
|
|
114
|
+
own home-grown CLI, anything that drops JSON/JSONL on disk.
|
|
115
|
+
|
|
67
116
|
---
|
|
68
117
|
|
|
69
118
|
## Install
|
|
@@ -185,6 +234,7 @@ flavors:
|
|
|
185
234
|
| Claude Code | `loop-memory hook --source claude --watch ~/.claude` |
|
|
186
235
|
| Hermes | `loop-memory hook --source hermes --watch ~/.hermes` |
|
|
187
236
|
| OpenClaw (clawx) | `loop-memory hook --source openclaw --watch ~/.openclaw/agents/main/sessions` — also ingests `workspace/memory/*.md` daily logs |
|
|
237
|
+
| Anything else (Aider, Cursor, Copilot, Cline, Continue, Goose, …) | `loop-memory hook --source <name> --watch <path/to/transcripts>` — see [docs/auto-capture.md](docs/auto-capture.md) for loader requirements |
|
|
188
238
|
|
|
189
239
|
Three of these in a `tmux` session, or persisted via launchd, keeps
|
|
190
240
|
your memory store fresh without any clicks. Run `loop-memory
|
|
@@ -291,6 +341,34 @@ User signals close the loop:
|
|
|
291
341
|
- Every `recall()` / search bumps `recall_count` on the returned
|
|
292
342
|
rows so the next Stage-1 ranks them higher.
|
|
293
343
|
|
|
344
|
+
### Cognitive sleep (v7)
|
|
345
|
+
|
|
346
|
+
`loop-memory cognitive-sleep [--apply]` runs an **auditable cleanup
|
|
347
|
+
pass** over the store:
|
|
348
|
+
|
|
349
|
+
- Surfaces contradictions between memories (e.g. "user prefers X"
|
|
350
|
+
vs. "user prefers Y") so you can resolve them in one click rather
|
|
351
|
+
than chasing them across sessions.
|
|
352
|
+
- Drops items that are below the configurable floor (`min_score`) and
|
|
353
|
+
older than the floor age.
|
|
354
|
+
- Merges near-duplicate memories (cosine ≥ 0.95 with `MergeableBy` rules).
|
|
355
|
+
- Emits a full audit row per action — visible in the Dashboard →
|
|
356
|
+
**Audit** tab and reachable via the MCP `audit` tool, so every
|
|
357
|
+
byte the consolidator ever touches is traceable.
|
|
358
|
+
|
|
359
|
+
Dry-run by default; pass `--apply` to commit.
|
|
360
|
+
|
|
361
|
+
### Knowledge graph
|
|
362
|
+
|
|
363
|
+
`loop-memory graph-rebuild` extracts entities from every distilled
|
|
364
|
+
wiki page and every long-term memory, then materialises a typed
|
|
365
|
+
relation graph:
|
|
366
|
+
|
|
367
|
+
- Visible as the **Knowledge graph** globe tab in the web UI.
|
|
368
|
+
- Queryable via the MCP `subgraph` and `remember_edge` tools.
|
|
369
|
+
- Re-built by the Evolution Consolidator's Stage-5 evolution memo, so
|
|
370
|
+
the graph evolves alongside the wiki.
|
|
371
|
+
|
|
294
372
|
## Auto-feedback into every LLM client (反哺)
|
|
295
373
|
|
|
296
374
|
Distilled knowledge is only useful if your LLM tools can actually
|
|
@@ -347,21 +425,6 @@ configuration, scheduling, language switching, and light/dark themes.
|
|
|
347
425
|
|
|
348
426
|
---
|
|
349
427
|
|
|
350
|
-
## Time-weighted scoring
|
|
351
|
-
|
|
352
|
-
Every memory carries a `score ∈ [0, 1]` recomputed from:
|
|
353
|
-
|
|
354
|
-
```
|
|
355
|
-
score = 0.35 · importance + 0.65 · recency
|
|
356
|
-
recency = ½ ^ (age / half_life)
|
|
357
|
-
```
|
|
358
|
-
|
|
359
|
-
`half_life` defaults to 30 days, configurable via
|
|
360
|
-
`consolidate(half_life_days=...)`. The UI shows the score as a
|
|
361
|
-
percentage; use `?min_score=0.85` to see only high-relevance memories.
|
|
362
|
-
|
|
363
|
-
---
|
|
364
|
-
|
|
365
428
|
## Programmatic use
|
|
366
429
|
|
|
367
430
|
```python
|
|
@@ -415,26 +478,35 @@ Even though v0.2 is built around local storage, the original
|
|
|
415
478
|
```
|
|
416
479
|
loop_memory/
|
|
417
480
|
loop_memory/
|
|
481
|
+
cli/main.py # CLI entrypoint + COMMAND_HELP table + `--version`
|
|
482
|
+
ingest/ # Codex / Claude / Hermes / OpenClaw / generic loaders
|
|
483
|
+
wiki/ # distillation, classifier, scope auto-promotion
|
|
484
|
+
graph/ # entity extraction + knowledge-graph build
|
|
485
|
+
jobs/ # consolidate / evolve / cognitive-sleep / scheduler / contradiction / graph
|
|
486
|
+
llm/ # provider protocol + OpenAI / Anthropic / Ollama / rule-based
|
|
487
|
+
backends/ # embedding (hashing / sentence-transformers) + vector store (memory / chroma)
|
|
488
|
+
storage/ # SQLite-backed MemoryStore + migrations
|
|
489
|
+
privacy/ # <private> stripping + regex redaction
|
|
490
|
+
security/ # Keychain / 0600-file secrets wrapper
|
|
491
|
+
mcp/ # stdio JSON-RPC MCP server
|
|
492
|
+
serve/ # FastAPI app, watcher, web UI (Timeline / Dashboard / Wiki / Graph)
|
|
493
|
+
export/ # markdown + v7 portable bundle export/import
|
|
494
|
+
sdk.py # four-verb stable API: remember / recall / forget / feedback
|
|
495
|
+
sdk_extensions.py # optional high-level helpers (graph edges, wiki pages)
|
|
496
|
+
engine/loop.py # Retrieve → Generate → Reflect → Store loop
|
|
418
497
|
memory/types.py # MemoryItem + 4 tiers
|
|
419
|
-
backends/embedding.py # BaseEmbedder, HashingEmbedder, IdentityEmbedder
|
|
420
|
-
backends/vector_store.py # VectorStore protocol + InMemory / Chroma
|
|
421
|
-
backends/sentence_embedder.py # optional sentence-transformers
|
|
422
|
-
llm/base.py # LLMClient protocol + EchoLLM + helpers
|
|
423
|
-
llm/openai_adapter.py # optional OpenAI client
|
|
424
|
-
engine/loop.py # the Retrieve → Generate → Reflect → Store loop
|
|
425
|
-
engine/reflect.py # reflection & summarization passes
|
|
426
|
-
storage/sqlite_store.py # persistent SQLite-backed MemoryStore
|
|
427
|
-
ingest/loader.py # CodexLoader, ClaudeLoader, HermesLoader
|
|
428
|
-
ingest/pipeline.py # session → MemoryStore
|
|
429
|
-
jobs/consolidate.py # background rescore + GC + dedupe
|
|
430
|
-
serve/app.py # FastAPI app for the local web UI
|
|
431
|
-
serve/static/index.html # the page
|
|
432
|
-
serve/watcher.py # filesystem watcher for auto-capture
|
|
433
|
-
cli/main.py # CLI entrypoint (chat / stats / ingest / consolidate / serve / hook)
|
|
434
498
|
examples/demo.py # runnable, zero-API-key demo
|
|
435
499
|
py.typed
|
|
436
|
-
tests/ #
|
|
437
|
-
docs/
|
|
500
|
+
tests/ # 475 unit tests across memory / SDK / serve / CLI / scripts
|
|
501
|
+
docs/
|
|
502
|
+
auto-capture.md # launchd / systemd / cron recipes
|
|
503
|
+
architecture.md # layered view + 5-stage evolution pipeline
|
|
504
|
+
api.md # HTTP API reference
|
|
505
|
+
agent-memory-api.md # four-verb SDK / HTTP / MCP contract
|
|
506
|
+
universal-agent-memory.md # v7 graph memory + cognitive sleep + bundles
|
|
507
|
+
providers.md # LLM provider reference
|
|
508
|
+
settings.md # settings table + secrets file
|
|
509
|
+
weekly-research-automation.md # how the project auto-evolves from upstream research
|
|
438
510
|
```
|
|
439
511
|
|
|
440
512
|
---
|
|
@@ -516,6 +588,67 @@ classifier is local and makes no model or network request on a wiki write.
|
|
|
516
588
|
|
|
517
589
|
---
|
|
518
590
|
|
|
591
|
+
## FAQ & troubleshooting
|
|
592
|
+
|
|
593
|
+
**Q: `pip install loop-memory` succeeds but `loop-memory serve` says `ModuleNotFoundError: No module named 'fastapi'`.**
|
|
594
|
+
A: `fastapi` is the optional `[serve]` extra. Install it explicitly:
|
|
595
|
+
`pip install 'loop-memory[serve]'` (or `'loop-memory[all]'` for everything).
|
|
596
|
+
|
|
597
|
+
**Q: My `~/.codex/sessions/` is empty / nothing appears in the UI.**
|
|
598
|
+
A: Run `loop-memory doctor` — it prints per-source paths, last-seen
|
|
599
|
+
mtime, and whether the watcher is running. Then check
|
|
600
|
+
`loop-memory hook --source codex --watch ~/.codex/sessions` is alive
|
|
601
|
+
in another shell (or via launchd — see [docs/auto-capture.md](docs/auto-capture.md)).
|
|
602
|
+
|
|
603
|
+
**Q: Distillation never finishes / wiki stays empty.**
|
|
604
|
+
A: You need an LLM provider configured. Open the web UI → ⚙ Model,
|
|
605
|
+
pick a provider, paste an API key, and click **Save**. Then either
|
|
606
|
+
wait for the scheduler or hit **� Run now**. Zero-deps installs ship
|
|
607
|
+
with a rule-based provider as a placeholder so the loop never blocks
|
|
608
|
+
on a missing key.
|
|
609
|
+
|
|
610
|
+
**Q: `loop-memory install-hooks` warns that the token file already exists.**
|
|
611
|
+
A: That's expected — it's idempotent. To force a rewrite, delete the
|
|
612
|
+
target files (`~/.codex/config.toml`, `~/.claude/mcp.json`,
|
|
613
|
+
`~/.hermes/mcp.json`) and re-run. The tool also refuses to touch
|
|
614
|
+
non-loop-memory config keys.
|
|
615
|
+
|
|
616
|
+
**Q: How big can the SQLite store get before I should worry?**
|
|
617
|
+
A: Practical floor: 100k memories / 10k wiki pages stays under ~80 MB
|
|
618
|
+
and `recall()` returns in <100 ms. The Evolution Consolidator is
|
|
619
|
+
designed to keep the wiki tight (~hundreds of pages) rather than let
|
|
620
|
+
it grow unbounded. Run `loop-memory cognitive-sleep --apply` weekly
|
|
621
|
+
to drop the long tail.
|
|
622
|
+
|
|
623
|
+
**Q: Can I sync the store across machines?**
|
|
624
|
+
A: The SQLite file is git-friendly and copy-friendly. The `MEMORY.md`
|
|
625
|
+
+ graph + memories + metadata **bundle** (`loop-memory export
|
|
626
|
+
<dir>`) is a portable v7 artefact you can commit, share, or
|
|
627
|
+
back-up. There is no first-class sync daemon — by design — so the
|
|
628
|
+
local-first guarantee is never violated.
|
|
629
|
+
|
|
630
|
+
**Q: Is there a hosted / cloud version?**
|
|
631
|
+
A: No. Loop Memory is MIT-licensed and 100% local; the SQLite file
|
|
632
|
+
lives under `~/.loop_memory/`. The web UI is bound to loopback by
|
|
633
|
+
default; binding to `0.0.0.0` prints a security warning and requires
|
|
634
|
+
an auth token.
|
|
635
|
+
|
|
636
|
+
**Q: Where do secrets / API keys live?**
|
|
637
|
+
A: Two places. Provider keys you set in the **⚙ Model** UI are
|
|
638
|
+
written to `~/.loop_memory/secrets.json` (mode 0600) via the
|
|
639
|
+
`loop_memory.security.secrets` wrapper, which prefers the macOS
|
|
640
|
+
Keychain on Darwin and falls back to the encrypted file on Linux.
|
|
641
|
+
The auth token used by the web UI is hashed in the SQLite settings
|
|
642
|
+
table — never stored in plaintext.
|
|
643
|
+
|
|
644
|
+
**Q: I see "no version" / "package not found" on the PyPI badge.**
|
|
645
|
+
A: shields.io pulls from a separate data source that lags PyPI by a
|
|
646
|
+
few minutes after a new release. Re-publish the badge warmer step
|
|
647
|
+
in `.github/workflows/publish.yml` to force a refresh, or wait ~30
|
|
648
|
+
minutes for shields.io to catch up.
|
|
649
|
+
|
|
650
|
+
---
|
|
651
|
+
|
|
519
652
|
## Run the tests
|
|
520
653
|
|
|
521
654
|
```bash
|