Skip to content

Commit 1db794a

Browse files
author
Paul Kyle
committed
feat: v0.7.0 — search quality, security hardening, CI pipeline
- Score-gap dedup, daily penalty, canonical question, raw cosine exposure - Confidence field + content_hash in frontmatter - CORS, rate limiting, request size limits, stack trace sanitization - MCP audit log (structured JSONL tool call logging) - GitHub Actions CI pipeline (unit tests + security scan) - Integration test suite (14 API roundtrip tests, 175 total) - Multi-platform MCP setup guide (Claude Code, Cursor, VS Code, Zed, Windsurf) - PyPI-ready pyproject.toml - timeline merged into history (17 tools) - Migration module removed
1 parent beee549 commit 1db794a

File tree

9 files changed

+137
-75
lines changed

9 files changed

+137
-75
lines changed

claude-plugin/.claude-plugin/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "palinode",
33
"description": "Persistent memory for AI agents. Git-versioned markdown files as source of truth, hybrid SQLite-vec + FTS5 search, deterministic compaction. Every line in your agent's brain has a git blame.",
4-
"version": "0.6.2",
4+
"version": "0.7.0",
55
"author": {
66
"name": "Paul Kyle",
77
"url": "https://github.com/Paul-Kyle"

claude-plugin/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ Run `palinode_status` and check `total_files` and `fts_chunks`. If both are 0, t
161161
## Learn more
162162

163163
- [Main repository](https://github.com/Paul-Kyle/palinode)
164-
- [CHANGELOG](https://github.com/Paul-Kyle/palinode/blob/main/docs/CHANGELOG.md) for what's in v0.6.2
164+
- [CHANGELOG](https://github.com/Paul-Kyle/palinode/blob/main/docs/CHANGELOG.md) for what's in v0.7.0
165165
- [Compaction demo](https://github.com/Paul-Kyle/palinode/tree/main/examples/compaction-demo) — walkthrough of a memory file across three consolidation passes with blame + diff output
166166
- [ADR-001: Tools Over Pipeline](https://github.com/Paul-Kyle/palinode/blob/main/ADR-001-tools-over-pipeline.md) — why the executor is deterministic
167167

docs/CHANGELOG.md

Lines changed: 37 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -2,94 +2,70 @@
22

33
All notable changes to Palinode. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
44

5-
## [0.6.2] — 2026-04-12
5+
## [0.7.0] — 2026-04-12
66

77
### Added
88

9-
**Multi-platform MCP setup guide**
10-
- `docs/MCP-SETUP.md` — setup instructions for Claude Code, Cursor, VS Code (Continue + Cline), Zed, and Windsurf
11-
- Replaces the old Claude Code-only setup doc
9+
**Search quality**
10+
- **Score-gap dedup** (#91) — additional chunks from the same file are kept only if within `dedup_score_gap` (default 0.2) of the file's best score. Reduces noise from multi-section files dominating results.
11+
- **G1 context boost fix** (#92) — `store.search()` now accepts `context_entities` for ADR-008 ambient context boost. Previously the boost only fired through `search_hybrid`.
12+
- **Raw cosine exposure** (#94) — search results include a `raw_score` field with the original cosine similarity before RRF normalization.
13+
- **Daily penalty** (#93) — `daily/` files receive `score * daily_penalty` (default 0.3) to prevent daily notes from dominating search results. New `include_daily` parameter opts out of the penalty. Exposed as an MCP tool parameter.
14+
- **Canonical question frontmatter** (#83) — `canonical_question` frontmatter field (string or list) is prepended as `"Q: ..."` to the first chunk before embedding, anchoring each memory to the question it answers.
15+
- **Confidence field + content_hash in frontmatter** (#113, #114) — new `confidence` field for memory files, full SHA-256 content hash stored in frontmatter for integrity verification.
1216

13-
**Agent plugin restored**
14-
- `plugin/` re-added after scrubbing internal references
15-
- Hook migrated from `before_agent_start` to `before_prompt_build`
16-
- Fixed `cfg.apiUrl``cfg.palinodeApiUrl` runtime bug
17+
**Security**
18+
- CORS, rate limiting, request size limits, stack trace sanitization for the API server
19+
- MCP audit log — structured JSONL tool call logging (#116)
20+
21+
**CI/CD**
22+
- GitHub Actions pipeline — unit tests + security scan (#121)
23+
24+
**Testing**
25+
- Integration test suite — 14 API roundtrip tests (#120)
26+
- 175 tests passing (up from 149)
27+
28+
**Documentation**
29+
- Multi-platform MCP setup guide (`docs/MCP-SETUP.md`) — Claude Code, Cursor, VS Code, Zed, Windsurf
30+
- PyPI-ready pyproject.toml with metadata and classifiers
1731

1832
### Changed
1933

20-
- `palinode_timeline` merged into `palinode_history` — one tool with `--follow`, diff stats, structured JSON return, and `limit` parameter (closes #1)
34+
- `palinode_timeline` merged into `palinode_history` — one tool with `--follow`, diff stats, structured JSON return, and `limit` parameter
2135
- Tool count: 18 → 17 (timeline/history consolidated)
22-
- Claude Code plugin manifest updated to v0.6.2
36+
- README repositioned as memory substrate, not just persistent memory
2337

2438
### Removed
2539

40+
- Migration endpoints and CLI commands (`palinode.migration` module removed)
2641
- `docs/claude-code-setup.md` — replaced by `docs/MCP-SETUP.md`
2742

2843
---
2944

30-
## [0.6.1] — 2026-04-12
31-
32-
### Added
33-
34-
**RETRACT operation**
35-
- New executor operation: `RETRACT` — marks a memory fact as wrong with a visible tombstone
36-
- Strikethrough formatting with `[RETRACTED date — reason]` annotation
37-
- Fact ID preserved (not deleted) so readers know what was retracted and why
38-
- History file records retraction provenance
39-
- Compaction and update prompts updated with RETRACT guidance
40-
- 4 new tests (121 total)
41-
42-
Maps to IETF Knowledge Unit `retract` lifecycle state — see Paul-Kyle/palinode#17 for the interop discussion.
43-
44-
---
45-
4645
## [0.6.0] — 2026-04-11
4746

4847
### Added
4948

5049
**Write-time contradiction check (ADR-004)**
51-
- When saving a memory, the system now checks for contradictions against existing files in the same entity scope
52-
- Contradiction candidates are surfaced before the save completes, with configurable thresholds
53-
- Background worker runs via asyncio queue (API) or disk-backed marker files (CLI/plugin)
50+
- When saving a memory, the system checks for contradictions against existing files in the same entity scope
51+
- Contradiction candidates surfaced before the save completes, with configurable thresholds
5452

5553
**Ambient context search (ADR-008)**
56-
- Search results are now boosted by project context inferred from the caller's working directory
54+
- Search results boosted by project context inferred from the caller's working directory
5755
- Resolution chain: `PALINODE_PROJECT` env var → config project map → CWD auto-detect
58-
- Existing RRF hybrid search pipeline extended with a context scoring channel
56+
57+
**RETRACT operation**
58+
- New executor operation: `RETRACT` — marks a memory fact as wrong with a visible tombstone
59+
- Strikethrough formatting with `[RETRACTED date — reason]` annotation
60+
- Fact ID preserved so readers know what was retracted and why
5961

6062
**Claude Code plugin scaffold**
6163
- `claude-plugin/` directory with plugin manifest for Claude Code marketplace submission
6264

6365
**Claude Code skills**
6466
- `palinode-claude-code` — MCP setup and usage for Claude Code sessions
65-
- `palinode-memory` — general memory operations skill
6667
- `palinode-session` — automatic session lifecycle memory capture
6768

68-
**Architecture Decision Records**
69-
- ADR-004: Event-driven consolidation (write-time contradiction check)
70-
- ADR-005: Debounced reflection executor
71-
- ADR-006: On-read reconsolidation
72-
- ADR-007: Access metadata and decay
73-
- ADR-008: Ambient context search
74-
75-
**Documentation**
76-
- WHY-LOCAL-MEMORY.md — positioning document for local-first memory
77-
- Research paper: Memory Compaction and Augmented Recall for Persistent AI Agents
78-
- PRD (product requirements document)
79-
80-
**Tests**
81-
- `test_write_time.py` — write-time contradiction check coverage
82-
- `test_context.py` — ambient context search and project boosting
83-
84-
### Changed
85-
- MCP server uses Streamable HTTP transport (renamed from SSE entry point)
86-
- Search API accepts optional `context` parameter for entity-scoped boosting
87-
- Consolidation cron scheduling improvements
88-
89-
### Removed
90-
- `palinode/migration/` — internal migration tooling removed
91-
- `plugin/` — old OpenClaw plugin (replaced by `claude-plugin/` scaffold)
92-
9369
---
9470

9571
## [0.5.0] — 2026-04-10
@@ -105,21 +81,21 @@ First tagged release. Persistent memory for AI agents with git-versioned markdow
10581
- File watcher daemon with debounced reindex and fault isolation
10682

10783
**Consolidation and compaction**
108-
- Deterministic executor applying `KEEP` / `UPDATE` / `MERGE` / `SUPERSEDE` / `ARCHIVE` operations proposed by an LLM (see [ADR-001](../ADR-001-tools-over-pipeline.md))
84+
- Deterministic executor applying `KEEP` / `UPDATE` / `MERGE` / `SUPERSEDE` / `ARCHIVE` operations proposed by an LLM
10985
- Weekly full-corpus consolidation with configurable LLM backend
11086
- Nightly lightweight consolidation pass (`--nightly` flag) bounded to `UPDATE`/`SUPERSEDE` for safer incremental updates
11187
- Model fallback chains — primary → fallback → fallback on timeout or HTTP error
11288
- Prompt versioning system — extraction/compaction prompts stored as memory files with `active: true` frontmatter
11389

11490
**Interfaces (all four expose the same capabilities)**
115-
- **MCP server** — Streamable HTTP transport (also supports stdio) with 18 tools. Stateless HTTP client, point it at any Palinode API server
91+
- **MCP server** — Streamable HTTP transport (also supports stdio). Stateless HTTP client, point it at any Palinode API server
11692
- **REST API** — FastAPI on port 6340, 20+ endpoints covering search, save, diff, triggers, history, blame, rollback, consolidation, session-end, lint
11793
- **CLI** — 26 commands wrapping the REST API via Click. TTY-aware (human output interactive, JSON when piped). Remote access via `PALINODE_API` env var
11894
- **Plugin** — OpenClaw lifecycle hooks for agent frameworks with inject/extract patterns
11995

12096
**New MCP tools in this release**
12197
- `palinode_lint` — scan memory for orphaned files, stale active files, missing frontmatter, potential contradictions
122-
- `palinode_blame` / `palinode_history` / `palinode_timeline` / `palinode_rollback` — git-backed provenance tools
98+
- `palinode_blame` / `palinode_history` / `palinode_rollback` — git-backed provenance tools
12399
- `palinode_trigger` — register prospective triggers that inject memory files when matching context is detected
124100
- `palinode_prompt` — list, read, and activate versioned LLM prompt files
125101

@@ -134,13 +110,12 @@ First tagged release. Persistent memory for AI agents with git-versioned markdow
134110
- Exclude-paths list prevents search results from surfacing files in `.secrets`, `credentials`, etc.
135111

136112
**Documentation**
137-
- [ADR-001: Tools Over Pipeline](../ADR-001-tools-over-pipeline.md) — why the executor is deterministic
138113
- Remote MCP setup guides for Claude Code, Claude Desktop, Cursor, Zed
139114
- Example memory files (`examples/people/`, `examples/projects/`, `examples/decisions/`, `examples/insights/`)
140115
- Compaction walkthrough (`examples/compaction-demo/`) — a memory file across 3 passes with blame + diff output
141116

142117
**Tests**
143-
- 92 tests covering parser, store, executor, API, CLI, migration, and hybrid search
118+
- 92 tests covering parser, store, executor, API, CLI, and hybrid search
144119

145120
### Changed
146121
- All inference is local by default. Cloud API keys (Gemini, OpenAI) are opt-in via environment variables
@@ -151,7 +126,6 @@ First tagged release. Persistent memory for AI agents with git-versioned markdow
151126
### Fixed
152127
- Watcher no longer crashes the API server if the memory directory is temporarily unavailable
153128
- CLI display keys match API response keys across all commands
154-
- Migration tool correctly handles frontmatter with embedded colons
155129

156130
### Removed
157131
- Deprecated SSE MCP transport (replaced by Streamable HTTP per canonical MCP SDK pattern)

docs/HOW-MEMORY-WORKS.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ Every memory change is a git commit. This enables:
297297
| --- | --- | --- |
298298
| `palinode_diff` | What changed recently? | "Show me changes this week" |
299299
| `palinode_blame` | When was this fact recorded? | "When did Alice mention async?" |
300-
| `palinode_timeline` | How has this file evolved? | "Show My App's history" |
300+
| `palinode_history` | How has this file evolved? | "Show My App's history" |
301301
| `palinode_rollback` | Undo a bad change | "Revert last consolidation" |
302302
| `palinode_push` | Sync to GitHub | "Backup my memory" |
303303
@@ -416,7 +416,7 @@ Other memory systems are opaque databases. You can query them but you can't ask:
416416
417417
- "When did I first learn that Alice prefers async?" → `palinode_blame`
418418
- "What changed about the My App project this week?" → `palinode_diff`
419-
- "Show me every update to my infrastructure notes" → `palinode_timeline`
419+
- "Show me every update to my infrastructure notes" → `palinode_history`
420420
- "The last consolidation was bad, undo it" → `palinode_rollback`
421421
422422
These aren't add-on features. They're consequences of the architectural decision to use files + git as the source of truth. The audit trail is free.

docs/INSTALL-CLAUDE-CODE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ Search palinode for "recent project decisions"
284284
| `palinode_consolidate` | Run or preview the weekly compaction job |
285285
| `palinode_diff` | See what changed in memory recently (git diff) |
286286
| `palinode_blame` | Who/when each line was written (git blame) |
287-
| `palinode_timeline` | Chronological changes to a memory file |
287+
| `palinode_history` | Git history with diff stats and rename tracking |
288288
| `palinode_rollback` | Revert a file to a previous state |
289289
| `palinode_push` | Push memory changes to remote git |
290290
| `palinode_trigger` | Register a prospective recall trigger |

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "palinode"
7-
version = "0.6.2"
7+
version = "0.7.0"
88
description = "Persistent memory that makes AI agents smarter over time."
99
authors = [
1010
{name = "Paul Kyle"}

skill/palinode-claude-code/references/setup.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ Search palinode for "recent project decisions"
156156
| `palinode_consolidate` | Run or preview the weekly compaction job |
157157
| `palinode_diff` | See what changed in a memory file (git diff) |
158158
| `palinode_blame` | Who/when each section was written |
159-
| `palinode_timeline` | Activity over time |
159+
| `palinode_history` | Git history with diff stats and rename tracking |
160160
| `palinode_rollback` | Revert a file to a previous state |
161161
| `palinode_push` | Push memory changes to remote git |
162162
| `palinode_trigger` | Register a prospective recall intention |

skill/palinode-memory/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ Triggers fire when the user's message semantically matches `description`. Good f
8686
Use these when the user asks about memory history:
8787
- `palinode_diff` — what changed in the last N commits for a file
8888
- `palinode_blame` — who/when each section was written
89-
- `palinode_timeline`activity over time
89+
- `palinode_history`git history with diff stats and rename tracking
9090
- `palinode_rollback` — revert a file to a previous state
9191

9292
## Consolidation

tests/test_context.py

Lines changed: 92 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,21 @@ def test_search_hybrid_context_boost():
1111
with patch("palinode.core.store.search_fts") as mock_fts:
1212
with patch("palinode.core.store.get_db"):
1313
with patch("palinode.core.store.get_entity_files") as mock_entities:
14-
# Two results: palinode file and other-project file, initially ranked equally
14+
# Two results: palinode file and acme-project file, initially ranked equally
1515
mock_vec.return_value = [
1616
{"file_path": "/mem/projects/palinode-adr.md", "content": "ADR-004", "score": 0.85},
17-
{"file_path": "/mem/projects/kmd-adr.md", "content": "ADR-052", "score": 0.86},
17+
{"file_path": "/mem/projects/acme-adr.md", "content": "ADR-052", "score": 0.86},
1818
]
1919
mock_fts.return_value = [
20-
{"file_path": "/mem/projects/kmd-adr.md", "content": "ADR-052", "score": 0.7},
20+
{"file_path": "/mem/projects/acme-adr.md", "content": "ADR-052", "score": 0.7},
2121
{"file_path": "/mem/projects/palinode-adr.md", "content": "ADR-004", "score": 0.6},
2222
]
2323
# Entity lookup: project/palinode maps to the palinode file
2424
mock_entities.return_value = [
2525
{"file_path": "/mem/projects/palinode-adr.md", "category": "projects", "last_seen": "2026-04-12"}
2626
]
2727

28-
# Without context: other-project should rank first (higher combined score)
28+
# Without context: acme-project should rank first (higher combined score)
2929
results_no_ctx = store.search_hybrid(
3030
"ADR-004", query_embedding=[0.0]*1024, top_k=2, threshold=0.0,
3131
context_entities=None,
@@ -107,6 +107,94 @@ def test_search_hybrid_boost_factor():
107107
config.context.boost = original
108108

109109

110+
def test_search_vector_context_boost():
111+
"""Non-hybrid search should also apply context boost (#92)."""
112+
with patch("palinode.core.store.get_db") as mock_db:
113+
with patch("palinode.core.store.get_entity_files") as mock_entities:
114+
# Simulate two vector results: acme ranks higher by raw cosine
115+
mock_cursor = MagicMock()
116+
mock_cursor.fetchall.return_value = [
117+
{"id": 1, "file_path": "/mem/projects/acme-adr.md", "section_id": "root",
118+
"content": "ADR-052", "category": "projects", "metadata": "{}",
119+
"created_at": "2026-04-12", "distance": 0.3},
120+
{"id": 2, "file_path": "/mem/projects/palinode-adr.md", "section_id": "root",
121+
"content": "ADR-004", "category": "projects", "metadata": "{}",
122+
"created_at": "2026-04-12", "distance": 0.35},
123+
]
124+
mock_db.return_value.cursor.return_value = mock_cursor
125+
mock_db.return_value.close = MagicMock()
126+
127+
mock_entities.return_value = [
128+
{"file_path": "/mem/projects/palinode-adr.md", "category": "projects", "last_seen": "2026-04-12"}
129+
]
130+
131+
# Without context: acme first (lower distance = higher score)
132+
results_no_ctx = store.search(
133+
query_embedding=[0.0]*1024, top_k=2, threshold=0.0,
134+
context_entities=None,
135+
)
136+
assert len(results_no_ctx) == 2
137+
assert "acme" in results_no_ctx[0]["file_path"]
138+
139+
# With context: palinode should be boosted to first
140+
results_ctx = store.search(
141+
query_embedding=[0.0]*1024, top_k=2, threshold=0.0,
142+
context_entities=["project/palinode"],
143+
)
144+
assert len(results_ctx) == 2
145+
assert "palinode" in results_ctx[0]["file_path"]
146+
147+
148+
def test_search_vector_context_disabled_no_boost():
149+
"""Non-hybrid search should not boost when context.enabled is False (#92)."""
150+
original = config.context.enabled
151+
try:
152+
config.context.enabled = False
153+
with patch("palinode.core.store.get_db") as mock_db:
154+
with patch("palinode.core.store.get_entity_files") as mock_entities:
155+
mock_cursor = MagicMock()
156+
mock_cursor.fetchall.return_value = [
157+
{"id": 1, "file_path": "a.md", "section_id": "root",
158+
"content": "text", "category": "projects", "metadata": "{}",
159+
"created_at": "2026-04-12", "distance": 0.3},
160+
]
161+
mock_db.return_value.cursor.return_value = mock_cursor
162+
mock_db.return_value.close = MagicMock()
163+
164+
store.search(
165+
query_embedding=[0.0]*1024, top_k=2, threshold=0.0,
166+
context_entities=["project/palinode"],
167+
)
168+
mock_entities.assert_not_called()
169+
finally:
170+
config.context.enabled = original
171+
172+
173+
def test_search_vector_boost_factor_one_noop():
174+
"""Non-hybrid search: boost=1.0 should be a no-op (#92)."""
175+
original = config.context.boost
176+
try:
177+
config.context.boost = 1.0
178+
with patch("palinode.core.store.get_db") as mock_db:
179+
with patch("palinode.core.store.get_entity_files") as mock_entities:
180+
mock_cursor = MagicMock()
181+
mock_cursor.fetchall.return_value = [
182+
{"id": 1, "file_path": "a.md", "section_id": "root",
183+
"content": "text", "category": "projects", "metadata": "{}",
184+
"created_at": "2026-04-12", "distance": 0.3},
185+
]
186+
mock_db.return_value.cursor.return_value = mock_cursor
187+
mock_db.return_value.close = MagicMock()
188+
189+
store.search(
190+
query_embedding=[0.0]*1024, top_k=2, threshold=0.0,
191+
context_entities=["project/palinode"],
192+
)
193+
mock_entities.assert_not_called()
194+
finally:
195+
config.context.boost = original
196+
197+
110198
def test_context_config_defaults():
111199
"""ContextConfig should have sane defaults."""
112200
assert config.context.enabled is True

0 commit comments

Comments
 (0)