Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Changelog

## v2.7.3 — 2026-04-07

### Fixed
- **ContextEngine: `ownsCompaction: true`** — palaia now declares compaction ownership, preventing OpenClaw's built-in Pi auto-compaction from running in parallel with `palaia gc`. Without this flag, both compaction mechanisms could interfere with each other, leading to unpredictable context truncation.
- **Doctor phantom stale-tasks warning** — `_check_stale_unassigned_tasks` now reads entries through `Store.all_entries_unfiltered()` instead of scanning `.md` files directly. The previous approach could report entries invisible to `palaia list` (e.g. entries with empty/invalid scope), causing the doctor to warn about tasks that the user cannot see or act on.
- **Scope: empty/unknown scope no longer hides entries** — `can_access()` now treats empty or unrecognized scope values as `"team"` instead of returning `False`. Entries with missing or malformed scope are no longer silently invisible.

---

## v2.7.2 — 2026-04-06

### New Features
Expand Down
2 changes: 1 addition & 1 deletion SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: palaia
version: "2.7.2"
version: "2.7.3"
description: >
Local, crash-safe persistent memory for OpenClaw agents.
SQLite-backed by default. Semantic search, projects, scopes, auto-capture.
Expand Down
2 changes: 1 addition & 1 deletion packages/openclaw-plugin/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@byte5ai/palaia",
"version": "2.7.2",
"version": "2.7.3",
"description": "palaia memory backend for OpenClaw",
"main": "index.ts",
"openclaw": {
Expand Down
2 changes: 1 addition & 1 deletion packages/openclaw-plugin/skill/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: palaia
version: "2.7.2"
version: "2.7.3"
description: >
Local, crash-safe persistent memory for OpenClaw agents.
SQLite-backed by default. Semantic search, projects, scopes, auto-capture.
Expand Down
1 change: 1 addition & 0 deletions packages/openclaw-plugin/src/context-engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,7 @@ export function createPalaiaContextEngine(
id: "palaia",
name: "palaia Memory",
version: "2.3",
ownsCompaction: true,
},

/**
Expand Down
2 changes: 1 addition & 1 deletion palaia/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: palaia
version: "2.7.2"
version: "2.7.3"
description: >
Local, crash-safe persistent memory for OpenClaw agents.
SQLite-backed by default. Semantic search, projects, scopes, auto-capture.
Expand Down
2 changes: 1 addition & 1 deletion palaia/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@

from __future__ import annotations

__version__ = "2.7.2"
__version__ = "2.7.3"
__author__ = "byte5 GmbH"
64 changes: 38 additions & 26 deletions palaia/doctor/checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -1862,7 +1862,12 @@ def _check_claude_code_config(palaia_root: Path | None) -> dict[str, Any]:


def _check_stale_unassigned_tasks(palaia_root: Path | None) -> dict[str, Any]:
"""Check for auto-captured tasks without assignee/due_date older than 7 days."""
"""Check for auto-captured tasks without assignee/due_date older than 7 days.

Uses Store.all_entries_unfiltered() to ensure consistent entry discovery
with `palaia list`. Previous implementation scanned .md files directly,
which could report entries invisible to the user (scope filtering mismatch).
"""
if palaia_root is None:
return {
"name": "stale_unassigned_tasks",
Expand All @@ -1873,36 +1878,43 @@ def _check_stale_unassigned_tasks(palaia_root: Path | None) -> dict[str, Any]:

from datetime import datetime, timezone

from palaia.entry import parse_entry
from palaia.store import Store

now = datetime.now(tz=timezone.utc)
stale_ids: list[str] = []

for tier in ("hot", "warm"):
tier_dir = palaia_root / tier
if not tier_dir.exists():
continue
for p in tier_dir.glob("*.md"):
try:
text = p.read_text(encoding="utf-8")
meta, _body = parse_entry(text)
if meta.get("type") != "task":
continue
if meta.get("assignee") or meta.get("due_date"):
continue
raw_tags = meta.get("tags", [])
tags = raw_tags if isinstance(raw_tags, list) else str(raw_tags).split(",")
tags = [t.strip() for t in tags]
if "auto-capture" not in tags:
continue
created = meta.get("created", "")
if not created:
continue
created_dt = datetime.fromisoformat(created)
if (now - created_dt).days >= 7:
stale_ids.append(p.stem)
except Exception:
try:
store = Store(palaia_root)
all_entries = store.all_entries_unfiltered(include_cold=False)
except Exception:
return {
"name": "stale_unassigned_tasks",
"label": "Stale unassigned tasks",
"status": "ok",
"message": "Could not read store",
}

for meta, _body, _tier in all_entries:
try:
if meta.get("type") != "task":
continue
if meta.get("assignee") or meta.get("due_date"):
continue
raw_tags = meta.get("tags", [])
tags = raw_tags if isinstance(raw_tags, list) else str(raw_tags).split(",")
tags = [t.strip() for t in tags]
if "auto-capture" not in tags:
continue
created = meta.get("created", "")
if not created:
continue
created_dt = datetime.fromisoformat(created)
if (now - created_dt).days >= 7:
entry_id = meta.get("id", "")
if entry_id:
stale_ids.append(entry_id)
except Exception:
continue

if not stale_ids:
return {
Expand Down
8 changes: 7 additions & 1 deletion palaia/scope.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ def can_access(
else:
return False

# Normalize empty/missing scope to "team" — entries must never become
# invisible just because scope was written as an empty string.
if not entry_scope:
entry_scope = "team"

if entry_scope == "team":
return True
if entry_scope == "public":
Expand All @@ -87,7 +92,8 @@ def can_access(
# Legacy shared:X entries are accessible like team entries
if entry_scope.startswith(_LEGACY_SHARED_PREFIX):
return True
return False
# Unknown scope: treat as team (safe default, never hide entries)
return True


def is_exportable(scope: str) -> bool:
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "palaia"
version = "2.7.2"
version = "2.7.3"
description = "Local, cloud-free memory for OpenClaw agents."
readme = "README.md"
license = {text = "MIT"}
Expand Down
2 changes: 1 addition & 1 deletion skills/palaia/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: palaia
version: "2.7.2"
version: "2.7.3"
description: >
Local, crash-safe persistent memory for OpenClaw agents.
SQLite-backed by default. Semantic search, projects, scopes, auto-capture.
Expand Down
Loading