@byte5ai/palaia 2.5.1 → 2.7.1
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.
- package/README.md +4 -4
- package/index.ts +2 -2
- package/openclaw.plugin.json +3 -3
- package/package.json +2 -2
- package/skill/SKILL.md +82 -52
- package/src/config.ts +4 -1
- package/src/context-engine.ts +24 -11
- package/src/hooks/capture.ts +17 -20
- package/src/hooks/index.ts +17 -11
- package/src/hooks/recall.ts +10 -5
- package/src/hooks/session.ts +4 -3
- package/src/hooks/state.ts +4 -3
- package/src/runner.ts +4 -4
- package/src/tools.ts +40 -22
- package/src/types.ts +1 -1
package/README.md
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# @byte5ai/palaia
|
|
2
2
|
|
|
3
|
-
**
|
|
3
|
+
**palaia memory backend for OpenClaw.**
|
|
4
4
|
|
|
5
|
-
Replace OpenClaw's built-in `memory-core` with
|
|
5
|
+
Replace OpenClaw's built-in `memory-core` with palaia — local, cloud-free, WAL-backed agent memory with tier routing and semantic search.
|
|
6
6
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
# Install
|
|
10
|
+
# Install palaia (Python CLI)
|
|
11
11
|
pip install palaia
|
|
12
12
|
|
|
13
13
|
# Install the OpenClaw plugin
|
|
@@ -59,7 +59,7 @@ All options are optional — sensible defaults are used:
|
|
|
59
59
|
|
|
60
60
|
### `memory_search` (always available)
|
|
61
61
|
|
|
62
|
-
Semantically search
|
|
62
|
+
Semantically search palaia memory:
|
|
63
63
|
|
|
64
64
|
```
|
|
65
65
|
memory_search({ query: "deployment process", maxResults: 5, tier: "all" })
|
package/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @byte5ai/palaia —
|
|
2
|
+
* @byte5ai/palaia — palaia Memory Backend for OpenClaw
|
|
3
3
|
*
|
|
4
4
|
* Plugin entry point. Loaded by OpenClaw via jiti (no build step needed).
|
|
5
5
|
*
|
|
@@ -33,7 +33,7 @@ import type { OpenClawPluginApi, OpenClawPluginEntry } from "./src/types.js";
|
|
|
33
33
|
// Plugin entry point compatible with OpenClaw plugin-sdk definePluginEntry
|
|
34
34
|
const palaiaPlugin: OpenClawPluginEntry = {
|
|
35
35
|
id: "palaia",
|
|
36
|
-
name: "
|
|
36
|
+
name: "palaia Memory",
|
|
37
37
|
register(api: OpenClawPluginApi) {
|
|
38
38
|
// Issue #66: Plugin config is resolved GLOBALLY via api.pluginConfig.
|
|
39
39
|
// OpenClaw does NOT provide per-agent config resolution — all agents share the same
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "palaia",
|
|
3
|
-
"name": "
|
|
3
|
+
"name": "palaia Memory",
|
|
4
4
|
"kind": "memory",
|
|
5
5
|
"skills": ["./skill"],
|
|
6
6
|
"configSchema": {
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
},
|
|
13
13
|
"workspace": {
|
|
14
14
|
"type": "string",
|
|
15
|
-
"description": "
|
|
15
|
+
"description": "palaia workspace path (default: agent workspace)"
|
|
16
16
|
},
|
|
17
17
|
"tier": {
|
|
18
18
|
"type": "string",
|
|
@@ -106,7 +106,7 @@
|
|
|
106
106
|
},
|
|
107
107
|
"uiHints": {
|
|
108
108
|
"binaryPath": {
|
|
109
|
-
"label": "
|
|
109
|
+
"label": "palaia Binary Path",
|
|
110
110
|
"placeholder": "auto-detect"
|
|
111
111
|
},
|
|
112
112
|
"workspace": {
|
package/package.json
CHANGED
package/skill/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: palaia
|
|
3
|
-
version: "2.
|
|
3
|
+
version: "2.7.1"
|
|
4
4
|
description: >
|
|
5
5
|
Local, crash-safe persistent memory for OpenClaw agents.
|
|
6
6
|
SQLite-backed by default. Semantic search, projects, scopes, auto-capture.
|
|
@@ -15,26 +15,28 @@ metadata:
|
|
|
15
15
|
kind: pip
|
|
16
16
|
package: "palaia[fastembed]"
|
|
17
17
|
bins: ["palaia"]
|
|
18
|
-
label: "Install
|
|
18
|
+
label: "Install palaia with semantic search (pip)"
|
|
19
19
|
postInstall:
|
|
20
20
|
- command: "palaia init"
|
|
21
|
-
label: "Initialize
|
|
22
|
-
- command: "
|
|
23
|
-
label: "Install OpenClaw plugin
|
|
21
|
+
label: "Initialize palaia store"
|
|
22
|
+
- command: "openclaw plugins install @byte5ai/palaia"
|
|
23
|
+
label: "Install OpenClaw plugin"
|
|
24
24
|
postUpdate:
|
|
25
|
-
- command: "python3 -m pip install --upgrade 'palaia[fastembed]' &&
|
|
26
|
-
label: "Upgrade
|
|
25
|
+
- command: "python3 -m pip install --upgrade 'palaia[fastembed]' && openclaw plugins install @byte5ai/palaia && palaia doctor --fix && palaia warmup"
|
|
26
|
+
label: "Upgrade palaia with semantic search + plugin and run health checks"
|
|
27
27
|
postUpdateMessage: >
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
palaia has been updated to v2.7. New: WebUI memory explorer (`palaia ui`),
|
|
29
|
+
improved doctor checks (legacy files, plugin registration), search timeout
|
|
30
|
+
fix, security hardening (localhost-only UI).
|
|
31
|
+
Run `palaia doctor --fix` to upgrade.
|
|
30
32
|
plugin:
|
|
31
33
|
slot: memory
|
|
32
34
|
package: "@byte5ai/palaia"
|
|
33
35
|
---
|
|
34
36
|
|
|
35
|
-
#
|
|
37
|
+
# palaia v2.7 — Agent Memory Skill
|
|
36
38
|
|
|
37
|
-
|
|
39
|
+
palaia is the memory system for OpenClaw agents. It gives you persistent, searchable, crash-safe memory across sessions. You don't need to understand how it works — just use the commands below.
|
|
38
40
|
|
|
39
41
|
<!-- begin:install -->
|
|
40
42
|
## First-Time Setup
|
|
@@ -73,22 +75,15 @@ Note: `palaia[fastembed]` already includes sqlite-vec for native SIMD vector sea
|
|
|
73
75
|
After the CLI is installed, wire it into OpenClaw:
|
|
74
76
|
|
|
75
77
|
```bash
|
|
76
|
-
|
|
78
|
+
openclaw plugins install @byte5ai/palaia
|
|
77
79
|
```
|
|
78
80
|
|
|
79
|
-
Then activate in OpenClaw config
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
Add to `openclaw.json`:
|
|
85
|
-
```json
|
|
81
|
+
Then activate the memory slot in OpenClaw config:
|
|
82
|
+
```json5
|
|
83
|
+
// openclaw.json
|
|
86
84
|
{
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
"allow": ["..existing..", "palaia"],
|
|
90
|
-
"slots": { "memory": "palaia" },
|
|
91
|
-
"entries": { "palaia": { "enabled": true } }
|
|
85
|
+
plugins: {
|
|
86
|
+
slots: { memory: "palaia" }
|
|
92
87
|
}
|
|
93
88
|
}
|
|
94
89
|
```
|
|
@@ -106,7 +101,7 @@ Single-agent systems work with the default name. For multi-agent setups:
|
|
|
106
101
|
palaia init --agent YOUR_AGENT_NAME
|
|
107
102
|
```
|
|
108
103
|
|
|
109
|
-
## Updating
|
|
104
|
+
## Updating palaia
|
|
110
105
|
|
|
111
106
|
When the user asks to update, or when `palaia status` shows a version mismatch:
|
|
112
107
|
|
|
@@ -133,7 +128,7 @@ pipx install "palaia[fastembed]" --force
|
|
|
133
128
|
palaia doctor --fix
|
|
134
129
|
|
|
135
130
|
# Step 4: Update OpenClaw plugin (if installed)
|
|
136
|
-
|
|
131
|
+
openclaw plugins install @byte5ai/palaia
|
|
137
132
|
```
|
|
138
133
|
|
|
139
134
|
After the manual update, `palaia upgrade` will be available for future updates.
|
|
@@ -141,14 +136,19 @@ After the manual update, `palaia upgrade` will be available for future updates.
|
|
|
141
136
|
|
|
142
137
|
## Auto-Check (run every time this skill is loaded)
|
|
143
138
|
|
|
144
|
-
Before doing anything else with
|
|
139
|
+
Before doing anything else with palaia, run `palaia doctor` silently. If it reports ANY warnings, address them immediately by following `palaia doctor --fix` recommendations. Do not proceed with other palaia tasks until doctor reports no issues.
|
|
145
140
|
|
|
146
141
|
---
|
|
147
142
|
|
|
148
143
|
## Core Concepts
|
|
149
144
|
|
|
150
|
-
### Auto-Capture
|
|
151
|
-
Conversations are automatically captured when the OpenClaw plugin is active.
|
|
145
|
+
### Auto-Capture vs. Manual Write
|
|
146
|
+
Conversations are automatically captured when the OpenClaw plugin is active. Auto-capture handles routine knowledge extraction from conversations.
|
|
147
|
+
|
|
148
|
+
**Manual writes (`palaia write`) are higher value.** When you or the user identify something as especially important — a key decision, a reusable workflow, a critical fact from an external source — write it manually. Manually written entries are ranked higher than auto-captured ones in recall results, so they surface more prominently in future sessions. Use manual writes for:
|
|
149
|
+
- Important facts, decisions, or context from external sources
|
|
150
|
+
- Reusable processes and workflows (team runbooks)
|
|
151
|
+
- Sticky notes / reminders for future sessions (tasks)
|
|
152
152
|
|
|
153
153
|
### Three Tiers
|
|
154
154
|
- **HOT** — Active memories (< 7 days or frequently accessed). Always searched.
|
|
@@ -162,8 +162,8 @@ Conversations are automatically captured when the OpenClaw plugin is active. You
|
|
|
162
162
|
|
|
163
163
|
### Entry Types
|
|
164
164
|
- **memory** — Facts, decisions, learnings (default)
|
|
165
|
-
- **process** — Workflows, checklists, SOPs
|
|
166
|
-
- **task** —
|
|
165
|
+
- **process** — Workflows, checklists, SOPs (team runbooks)
|
|
166
|
+
- **task** — Sticky notes / reminders for future sessions. Tasks are ephemeral: when marked done, they are automatically deleted. Never auto-captured — only created by explicit `palaia write --type task`.
|
|
167
167
|
|
|
168
168
|
---
|
|
169
169
|
|
|
@@ -184,7 +184,7 @@ palaia config set database_url postgresql://user:pass@host/db
|
|
|
184
184
|
|
|
185
185
|
### Semantic Vector Search
|
|
186
186
|
|
|
187
|
-
|
|
187
|
+
palaia uses **hybrid search**: BM25 keyword matching (always active) combined with semantic vector embeddings (when a provider is configured). This finds memories by meaning, not just keywords.
|
|
188
188
|
|
|
189
189
|
**Embedding providers** (checked in chain order, first available wins):
|
|
190
190
|
|
|
@@ -215,7 +215,7 @@ The OpenClaw plugin starts the embed-server automatically. For CLI-only usage, i
|
|
|
215
215
|
|
|
216
216
|
### MCP Server (Claude Desktop, Cursor, any MCP host)
|
|
217
217
|
|
|
218
|
-
|
|
218
|
+
palaia works as a standalone MCP memory server — **no OpenClaw required**. Any AI tool that supports MCP can use palaia as persistent local memory.
|
|
219
219
|
|
|
220
220
|
```bash
|
|
221
221
|
pip install 'palaia[mcp]'
|
|
@@ -265,22 +265,28 @@ palaia-mcp --read-only # No writes (untrusted hosts)
|
|
|
265
265
|
|
|
266
266
|
### `palaia write` — Save structured knowledge
|
|
267
267
|
|
|
268
|
-
|
|
268
|
+
Use for important facts, reusable processes, sticky-note tasks, and knowledge from external sources. Manually written entries rank higher than auto-captured ones in recall.
|
|
269
269
|
|
|
270
270
|
```bash
|
|
271
|
-
# Save
|
|
271
|
+
# Save an important fact (ranks higher than auto-capture)
|
|
272
272
|
palaia write "API rate limit is 100 req/min" --type memory --tags api,limits
|
|
273
273
|
|
|
274
|
-
# Record a
|
|
275
|
-
palaia write "
|
|
274
|
+
# Record a reusable team workflow (use specific, unique titles!)
|
|
275
|
+
palaia write "Backend: Deploy to staging via Docker" --type process --project myapp --scope team
|
|
276
276
|
|
|
277
|
-
# Create a
|
|
278
|
-
palaia write "
|
|
277
|
+
# Create a sticky note for a future session (deleted when done)
|
|
278
|
+
palaia write "verify backup works after schema migration" --type task
|
|
279
279
|
|
|
280
280
|
# Save to a specific project with scope
|
|
281
281
|
palaia write "Use JWT for auth" --project backend --scope team --tags decision
|
|
282
282
|
```
|
|
283
283
|
|
|
284
|
+
**Process naming convention:** Use the format `[Domain]: [What it does]` for process titles. Specific titles prevent duplicates and make processes findable.
|
|
285
|
+
- Good: `"Release: PyPI publish + ClawHub sync"`, `"Backend: Deploy to staging via Docker"`
|
|
286
|
+
- Bad: `"Deploy steps"`, `"Release process"` (too generic, will collide with other similar processes)
|
|
287
|
+
|
|
288
|
+
Before writing a new process, search for existing ones: `palaia query "deploy" --type process`. If a similar process exists, update it with `palaia edit <id>` instead of creating a new one.
|
|
289
|
+
|
|
284
290
|
### `palaia query` — Semantic search
|
|
285
291
|
|
|
286
292
|
Find memories by meaning, not just keywords.
|
|
@@ -438,6 +444,17 @@ palaia upgrade
|
|
|
438
444
|
|
|
439
445
|
Auto-detects the install method (pip/uv/pipx/brew), preserves all installed extras (fastembed, mcp, sqlite-vec, curate), runs `palaia doctor --fix`, and upgrades the OpenClaw npm plugin if present. Always use this instead of manual pip commands.
|
|
440
446
|
|
|
447
|
+
### `palaia ui` — Local memory explorer (NEW in v2.7)
|
|
448
|
+
|
|
449
|
+
```bash
|
|
450
|
+
pip install 'palaia[ui]' # One-time: install FastAPI + uvicorn
|
|
451
|
+
palaia ui # Opens browser at http://127.0.0.1:8384
|
|
452
|
+
palaia ui --port 9000 # Custom port (auto-fallback if busy)
|
|
453
|
+
palaia ui --no-browser # Don't auto-open browser
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
Browse, search, create, edit, and delete entries in the browser. Manual entries are highlighted with a gold border (1.3× recall boost). Tasks are post-its: clicking ✓ deletes them. The health pill in the header shows doctor status with actionable warnings. Localhost only — no authentication, no network exposure.
|
|
457
|
+
|
|
441
458
|
### `palaia doctor` — Diagnostics and auto-fix
|
|
442
459
|
|
|
443
460
|
```bash
|
|
@@ -457,6 +474,15 @@ palaia gc --aggressive # Also clears COLD tier
|
|
|
457
474
|
palaia gc --budget 200 # Keep max N entries
|
|
458
475
|
```
|
|
459
476
|
|
|
477
|
+
### `palaia prune` — Selective cleanup (NEW in v2.5)
|
|
478
|
+
|
|
479
|
+
```bash
|
|
480
|
+
palaia prune --agent moneypenny # Remove auto-captured entries by agent
|
|
481
|
+
palaia prune --tags auto-capture # Remove all auto-captured entries
|
|
482
|
+
palaia prune --dry-run # Preview what would be removed
|
|
483
|
+
palaia prune --protect-type process # Never delete process entries
|
|
484
|
+
```
|
|
485
|
+
|
|
460
486
|
### `palaia config` — Configuration
|
|
461
487
|
|
|
462
488
|
```bash
|
|
@@ -486,10 +512,13 @@ palaia project locks # List all active locks
|
|
|
486
512
|
### `palaia edit` — Modify existing entries
|
|
487
513
|
|
|
488
514
|
```bash
|
|
489
|
-
palaia edit <id> --status done
|
|
515
|
+
palaia edit <id> --status done # For tasks: this DELETES the entry (sticky note completed)
|
|
516
|
+
palaia edit <id> --status wontfix # For tasks: also deletes (cancelled reminder)
|
|
490
517
|
palaia edit <id> "updated content" --tags new,tags --priority high
|
|
491
518
|
```
|
|
492
519
|
|
|
520
|
+
**Task lifecycle:** Tasks are sticky notes. When you mark a task as `done` or `wontfix`, it is automatically deleted — not archived. This is intentional: completed reminders have no long-term value. If the outcome of the task should be remembered, write a separate memory entry before marking the task done.
|
|
521
|
+
|
|
493
522
|
### Other commands
|
|
494
523
|
|
|
495
524
|
```bash
|
|
@@ -508,7 +537,7 @@ All commands support `--json` for machine-readable output.
|
|
|
508
537
|
|
|
509
538
|
## Smart Nudging
|
|
510
539
|
|
|
511
|
-
|
|
540
|
+
palaia's CLI output contains contextual hints prefixed with `[palaia]`. These are important guidance.
|
|
512
541
|
|
|
513
542
|
**As an agent, you should:**
|
|
514
543
|
- Read and act on nudge messages in CLI output
|
|
@@ -649,12 +678,12 @@ palaia write "## Dev-Agent Lifecycle
|
|
|
649
678
|
|
|
650
679
|
| Situation | Command |
|
|
651
680
|
|-----------|---------|
|
|
652
|
-
|
|
|
653
|
-
|
|
|
654
|
-
|
|
|
655
|
-
| Mark
|
|
681
|
+
| Save an important fact or decision | `palaia write "..." --type memory` (ranks higher than auto-capture) |
|
|
682
|
+
| Document a reusable workflow | `palaia write "Domain: Steps..." --type process --scope team` |
|
|
683
|
+
| Leave a reminder for future sessions | `palaia write "check X after Y" --type task` (auto-deleted when done) |
|
|
684
|
+
| Mark a reminder as done | `palaia edit <id> --status done` (deletes the task) |
|
|
656
685
|
| Find something | `palaia query "..."` |
|
|
657
|
-
| Find open
|
|
686
|
+
| Find open reminders | `palaia list --type task --status open` |
|
|
658
687
|
| Check system health | `palaia status` |
|
|
659
688
|
| Something is wrong | `palaia doctor --fix` |
|
|
660
689
|
| Clean up old entries | `palaia gc` |
|
|
@@ -662,11 +691,11 @@ palaia write "## Dev-Agent Lifecycle
|
|
|
662
691
|
| Review accumulated knowledge | `palaia curate analyze` |
|
|
663
692
|
| Share knowledge | `palaia sync export` or `palaia package export` |
|
|
664
693
|
| Check for messages | `palaia memo inbox` |
|
|
665
|
-
| Start of session | Session briefing is
|
|
694
|
+
| Start of session | Session briefing is automatic. Just run `palaia doctor` and check `palaia memo inbox`. |
|
|
666
695
|
|
|
667
|
-
**
|
|
696
|
+
**Auto-capture** handles routine conversation knowledge. **Manual writes rank higher** in recall — use them when something is especially important, reusable, or comes from an external source.
|
|
668
697
|
|
|
669
|
-
**DO manually write:**
|
|
698
|
+
**DO manually write:** key decisions, reusable processes (team runbooks), sticky-note reminders (tasks), important facts from external sources.
|
|
670
699
|
|
|
671
700
|
---
|
|
672
701
|
|
|
@@ -706,10 +735,10 @@ palaia init --capture-level <off|minimal|normal|aggressive>
|
|
|
706
735
|
Session continuity gives agents automatic context restoration across sessions. These features work out of the box with the OpenClaw plugin -- no manual setup needed.
|
|
707
736
|
|
|
708
737
|
### Session Briefings
|
|
709
|
-
|
|
738
|
+
Session briefings are injected automatically at session start. They contain your last session summary and any open tasks (sticky notes). Read the briefing carefully — it is your primary context for continuing previous work. Do not ask the user "where did we leave off?" when a briefing is present. Instead, acknowledge the context and continue seamlessly.
|
|
710
739
|
|
|
711
740
|
### Session Summaries
|
|
712
|
-
When a session ends or resets,
|
|
741
|
+
When a session ends or resets, palaia auto-saves a summary of what happened. These are stored as entries with the `session-summary` tag and can be queried:
|
|
713
742
|
```bash
|
|
714
743
|
palaia query "session-summary" --tags session-summary
|
|
715
744
|
```
|
|
@@ -721,7 +750,7 @@ Wrap sensitive content in `<private>...</private>` blocks to exclude it from aut
|
|
|
721
750
|
Fresh memories are ranked higher in recall results. The boost factor is configurable via `recallRecencyBoost` (default `0.3`, set to `0` to disable).
|
|
722
751
|
|
|
723
752
|
### Progressive Disclosure
|
|
724
|
-
When result sets exceed 100 entries,
|
|
753
|
+
When result sets exceed 100 entries, palaia uses compact mode to keep context manageable. Use `--limit` to control result size explicitly:
|
|
725
754
|
```bash
|
|
726
755
|
palaia list --type task --status open --limit 5
|
|
727
756
|
```
|
|
@@ -750,6 +779,7 @@ Set in `openclaw.json` under `plugins.entries.palaia.config`:
|
|
|
750
779
|
| `sessionBriefingMaxChars` | `1500` | Max chars for session briefing injection |
|
|
751
780
|
| `captureToolObservations` | `true` | Track tool usage as session context |
|
|
752
781
|
| `recallRecencyBoost` | `0.3` | Boost factor for fresh memories (0=off) |
|
|
782
|
+
| `manualEntryBoost` | `1.3` | Boost factor for manually written entries vs auto-captured (1.0=off) |
|
|
753
783
|
|
|
754
784
|
---
|
|
755
785
|
|
package/src/config.ts
CHANGED
|
@@ -12,7 +12,7 @@ export interface RecallTypeWeights {
|
|
|
12
12
|
export interface PalaiaPluginConfig {
|
|
13
13
|
/** Path to palaia binary (default: auto-detect) */
|
|
14
14
|
binaryPath?: string;
|
|
15
|
-
/**
|
|
15
|
+
/** palaia workspace path (default: agent workspace) */
|
|
16
16
|
workspace?: string;
|
|
17
17
|
/** Default tier filter: "hot" | "warm" | "all" */
|
|
18
18
|
tier: string;
|
|
@@ -74,6 +74,8 @@ export interface PalaiaPluginConfig {
|
|
|
74
74
|
captureToolObservations: boolean;
|
|
75
75
|
/** Recency boost factor for recall (0 = off, 0.3 = 30% boost for <24h entries) */
|
|
76
76
|
recallRecencyBoost: number;
|
|
77
|
+
/** Boost factor for manually written entries vs auto-captured (default: 1.3 = 30% boost) */
|
|
78
|
+
manualEntryBoost: number;
|
|
77
79
|
}
|
|
78
80
|
|
|
79
81
|
export const DEFAULT_RECALL_TYPE_WEIGHTS: RecallTypeWeights = {
|
|
@@ -103,6 +105,7 @@ export const DEFAULT_CONFIG: PalaiaPluginConfig = {
|
|
|
103
105
|
sessionBriefingMaxChars: 1500,
|
|
104
106
|
captureToolObservations: true,
|
|
105
107
|
recallRecencyBoost: 0.3,
|
|
108
|
+
manualEntryBoost: 1.3,
|
|
106
109
|
};
|
|
107
110
|
|
|
108
111
|
/**
|
package/src/context-engine.ts
CHANGED
|
@@ -33,7 +33,7 @@ import {
|
|
|
33
33
|
extractWithLLM,
|
|
34
34
|
shouldAttemptCapture,
|
|
35
35
|
extractSignificance,
|
|
36
|
-
|
|
36
|
+
strippalaiaInjectedContext,
|
|
37
37
|
stripPrivateBlocks,
|
|
38
38
|
trimToRecentExchanges,
|
|
39
39
|
parsePalaiaHints,
|
|
@@ -174,12 +174,12 @@ async function buildMemoryContext(
|
|
|
174
174
|
}
|
|
175
175
|
|
|
176
176
|
// Apply type-weighted reranking and blocked filtering (Issue #121)
|
|
177
|
-
const rankedRaw = rerankByTypeWeight(entries, resolvedPrio.recallTypeWeight, config.recallRecencyBoost);
|
|
177
|
+
const rankedRaw = rerankByTypeWeight(entries, resolvedPrio.recallTypeWeight, config.recallRecencyBoost, config.manualEntryBoost);
|
|
178
178
|
const ranked = filterBlocked(rankedRaw, resolvedPrio.blocked);
|
|
179
179
|
|
|
180
180
|
// Build context string — progressive disclosure for large stores
|
|
181
181
|
const compact = shouldUseCompactMode(ranked.length);
|
|
182
|
-
let text = "## Active Memory (
|
|
182
|
+
let text = "## Active Memory (palaia)\n\n";
|
|
183
183
|
if (compact) {
|
|
184
184
|
text += "_Compact mode — use `memory_get <id>` for full details._\n\n";
|
|
185
185
|
}
|
|
@@ -193,7 +193,7 @@ async function buildMemoryContext(
|
|
|
193
193
|
}
|
|
194
194
|
|
|
195
195
|
// Build nudge text and check remaining budget before appending
|
|
196
|
-
const USAGE_NUDGE = "[palaia] auto-capture=on. Manual
|
|
196
|
+
const USAGE_NUDGE = "[palaia] auto-capture=on. Manual writes rank higher in recall. Use --type process for reusable workflows, --type task for sticky-note reminders (auto-deleted when done), --type memory for important facts.";
|
|
197
197
|
let agentNudges = "";
|
|
198
198
|
try {
|
|
199
199
|
const pluginState = await loadPluginState(config.workspace);
|
|
@@ -203,7 +203,7 @@ async function buildMemoryContext(
|
|
|
203
203
|
}
|
|
204
204
|
const { nudges } = checkNudges(pluginState);
|
|
205
205
|
if (nudges.length > 0) {
|
|
206
|
-
agentNudges = "\n\n## Agent Nudge (
|
|
206
|
+
agentNudges = "\n\n## Agent Nudge (palaia)\n\n" + nudges.join("\n\n");
|
|
207
207
|
}
|
|
208
208
|
await savePluginState(pluginState, config.workspace);
|
|
209
209
|
} catch {
|
|
@@ -255,11 +255,17 @@ async function runAutoCapture(
|
|
|
255
255
|
logger: { info(...a: unknown[]): void; warn(...a: unknown[]): void },
|
|
256
256
|
): Promise<boolean> {
|
|
257
257
|
if (!config.autoCapture) return false;
|
|
258
|
-
if (!messages || messages.length === 0)
|
|
258
|
+
if (!messages || messages.length === 0) {
|
|
259
|
+
logger.info("[palaia] Auto-capture skipped: no messages");
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
259
262
|
|
|
260
263
|
const allTexts = extractMessageTexts(messages);
|
|
261
264
|
const userTurns = allTexts.filter((t) => t.role === "user").length;
|
|
262
|
-
if (userTurns < config.captureMinTurns)
|
|
265
|
+
if (userTurns < config.captureMinTurns) {
|
|
266
|
+
logger.info(`[palaia] Auto-capture skipped: ${userTurns} user turns < captureMinTurns=${config.captureMinTurns}`);
|
|
267
|
+
return false;
|
|
268
|
+
}
|
|
263
269
|
|
|
264
270
|
// Parse capture hints
|
|
265
271
|
const collectedHints: { project?: string; scope?: string }[] = [];
|
|
@@ -272,7 +278,7 @@ async function runAutoCapture(
|
|
|
272
278
|
const cleanedTexts = allTexts.map(t => ({
|
|
273
279
|
...t,
|
|
274
280
|
text: stripPrivateBlocks(
|
|
275
|
-
t.role === "user" ?
|
|
281
|
+
t.role === "user" ? strippalaiaInjectedContext(t.text) : t.text
|
|
276
282
|
),
|
|
277
283
|
}));
|
|
278
284
|
const recentTexts = trimToRecentExchanges(cleanedTexts);
|
|
@@ -283,7 +289,10 @@ async function runAutoCapture(
|
|
|
283
289
|
}
|
|
284
290
|
const exchangeText = exchangeParts.join("\n");
|
|
285
291
|
|
|
286
|
-
if (!shouldAttemptCapture(exchangeText))
|
|
292
|
+
if (!shouldAttemptCapture(exchangeText)) {
|
|
293
|
+
logger.info(`[palaia] Auto-capture skipped: content did not pass significance filter (${exchangeText.length} chars)`);
|
|
294
|
+
return false;
|
|
295
|
+
}
|
|
287
296
|
|
|
288
297
|
const hookOpts = buildRunnerOpts(config);
|
|
289
298
|
const knownProjects = await loadProjects(hookOpts);
|
|
@@ -295,6 +304,7 @@ async function runAutoCapture(
|
|
|
295
304
|
try {
|
|
296
305
|
const results = await extractWithLLM(messages, api.config, {
|
|
297
306
|
captureModel: config.captureModel,
|
|
307
|
+
workspace: config.workspace,
|
|
298
308
|
}, knownProjects);
|
|
299
309
|
|
|
300
310
|
for (const r of results) {
|
|
@@ -330,7 +340,10 @@ async function runAutoCapture(
|
|
|
330
340
|
if (!llmHandled) {
|
|
331
341
|
if (config.captureFrequency === "significant") {
|
|
332
342
|
const significance = extractSignificance(exchangeText);
|
|
333
|
-
if (!significance)
|
|
343
|
+
if (!significance) {
|
|
344
|
+
logger.info("[palaia] Auto-capture skipped: rule-based extraction found no significance (need ≥2 distinct tags)");
|
|
345
|
+
return false;
|
|
346
|
+
}
|
|
334
347
|
const tags = [...significance.tags];
|
|
335
348
|
if (!tags.includes("auto-capture")) tags.push("auto-capture");
|
|
336
349
|
const scope = effectiveCaptureScope !== "team"
|
|
@@ -379,7 +392,7 @@ export function createPalaiaContextEngine(
|
|
|
379
392
|
const engine: ContextEngine = {
|
|
380
393
|
info: {
|
|
381
394
|
id: "palaia",
|
|
382
|
-
name: "
|
|
395
|
+
name: "palaia Memory",
|
|
383
396
|
version: "2.3",
|
|
384
397
|
},
|
|
385
398
|
|
package/src/hooks/capture.ts
CHANGED
|
@@ -237,12 +237,8 @@ WHAT TO CAPTURE (be thorough — capture anything worth remembering):
|
|
|
237
237
|
- Project context changes: scope changes, timeline shifts, requirement updates, priority changes
|
|
238
238
|
- Workflow patterns the user established ("my process is...", "I always do X before Y")
|
|
239
239
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
2. An identifiable responsible party (explicitly named or unambiguously inferable from context)
|
|
243
|
-
3. A concrete deliverable or measurable end state
|
|
244
|
-
If ANY of these is missing, classify as "memory" instead of "task". When in doubt, use "memory".
|
|
245
|
-
Observations, learnings, insights, opinions, and general knowledge are ALWAYS "memory", never "task".
|
|
240
|
+
IMPORTANT: Never classify as "task". Tasks are manually created sticky notes (post-its) — they must only come from explicit user/agent intent via palaia write --type task. Auto-capture must use "memory" or "process" only.
|
|
241
|
+
Observations, learnings, insights, opinions, action items, and general knowledge are ALWAYS "memory" or "process", never "task".
|
|
246
242
|
|
|
247
243
|
Only extract genuinely significant knowledge. Skip small talk, acknowledgments, routine exchanges.
|
|
248
244
|
Do NOT extract if similar knowledge was likely captured in a recent exchange. Prefer quality over quantity. Skip routine status updates and acknowledgments.
|
|
@@ -461,7 +457,7 @@ export function trimToRecentExchanges(
|
|
|
461
457
|
export async function extractWithLLM(
|
|
462
458
|
messages: unknown[],
|
|
463
459
|
config: any,
|
|
464
|
-
pluginConfig?: { captureModel?: string },
|
|
460
|
+
pluginConfig?: { captureModel?: string; workspace?: string },
|
|
465
461
|
knownProjects?: CachedProject[],
|
|
466
462
|
): Promise<ExtractionResult[]> {
|
|
467
463
|
const runEmbeddedPiAgent = await getEmbeddedPiAgent();
|
|
@@ -472,10 +468,10 @@ export async function extractWithLLM(
|
|
|
472
468
|
}
|
|
473
469
|
|
|
474
470
|
const allTexts = extractMessageTexts(messages);
|
|
475
|
-
// Strip
|
|
471
|
+
// Strip palaia-injected recall context and private blocks from user messages
|
|
476
472
|
const cleanedTexts = allTexts.map(t =>
|
|
477
473
|
t.role === "user"
|
|
478
|
-
? { ...t, text: stripPrivateBlocks(
|
|
474
|
+
? { ...t, text: stripPrivateBlocks(strippalaiaInjectedContext(t.text)) }
|
|
479
475
|
: { ...t, text: stripPrivateBlocks(t.text) }
|
|
480
476
|
);
|
|
481
477
|
// Only extract from recent exchanges — full history causes LLM timeouts
|
|
@@ -519,7 +515,7 @@ export async function extractWithLLM(
|
|
|
519
515
|
const result = await runEmbeddedPiAgent({
|
|
520
516
|
sessionId,
|
|
521
517
|
sessionFile,
|
|
522
|
-
workspaceDir: config?.agents?.defaults?.workspace
|
|
518
|
+
workspaceDir: pluginConfig?.workspace || config?.agents?.defaults?.workspace || process.cwd(),
|
|
523
519
|
config,
|
|
524
520
|
prompt,
|
|
525
521
|
timeoutMs: 15_000,
|
|
@@ -551,7 +547,8 @@ export async function extractWithLLM(
|
|
|
551
547
|
const content = typeof item.content === "string" ? item.content.trim() : "";
|
|
552
548
|
if (!content) continue;
|
|
553
549
|
|
|
554
|
-
const validTypes = new Set(["memory", "process"
|
|
550
|
+
const validTypes = new Set(["memory", "process"]);
|
|
551
|
+
// Tasks are manual-only (post-its) — auto-capture never creates tasks
|
|
555
552
|
const type = validTypes.has(item.type) ? item.type : "memory";
|
|
556
553
|
|
|
557
554
|
const validTags = new Set([
|
|
@@ -614,9 +611,9 @@ const SIGNIFICANCE_RULES: Array<{
|
|
|
614
611
|
{ pattern: /(?:mistake was|fehler war|should have|hätten sollen|next time)/i, tag: "lesson", type: "memory" },
|
|
615
612
|
// Surprises
|
|
616
613
|
{ pattern: /(?:surprising|überraschend|unexpected|unerwartet|didn'?t expect|nicht erwartet|plot twist)/i, tag: "surprise", type: "memory" },
|
|
617
|
-
// Commitments
|
|
618
|
-
{ pattern: /(?:i will|ich werde|todo:|action item|must do|muss noch|need to|commit to|verspreche)/i, tag: "commitment", type: "
|
|
619
|
-
{ pattern: /(?:deadline|frist|due date|bis zum|by end of|spätestens)/i, tag: "commitment", type: "
|
|
614
|
+
// Commitments (captured as memory — tasks are manual-only post-its)
|
|
615
|
+
{ pattern: /(?:i will|ich werde|todo:|action item|must do|muss noch|need to|commit to|verspreche)/i, tag: "commitment", type: "memory" },
|
|
616
|
+
{ pattern: /(?:deadline|frist|due date|bis zum|by end of|spätestens)/i, tag: "commitment", type: "memory" },
|
|
620
617
|
// Processes and workflows
|
|
621
618
|
{ pattern: /(?:the process is|der prozess|steps?:|workflow:|how to|anleitung|recipe:|checklist)/i, tag: "process", type: "process" },
|
|
622
619
|
{ pattern: /(?:first,?\s.*then|schritt \d|step \d|1\.\s.*2\.\s)/i, tag: "process", type: "process" },
|
|
@@ -725,22 +722,22 @@ export function extractSignificance(
|
|
|
725
722
|
}
|
|
726
723
|
|
|
727
724
|
/**
|
|
728
|
-
* Strip
|
|
725
|
+
* Strip palaia-injected recall context from message text.
|
|
729
726
|
* The recall block is prepended to user messages by before_prompt_build via prependContext.
|
|
730
727
|
* OpenClaw merges it into the user message, so agent_end sees it as user content.
|
|
731
728
|
* Without stripping, auto-capture re-captures the injected memories -> feedback loop.
|
|
732
729
|
*
|
|
733
730
|
* The block has a stable structure:
|
|
734
|
-
* - Starts with "## Active Memory (
|
|
731
|
+
* - Starts with "## Active Memory (palaia)"
|
|
735
732
|
* - Contains [t/m], [t/pr], [t/tk] prefixed entries
|
|
736
733
|
* - Ends with "[palaia] auto-capture=on..." nudge line
|
|
737
734
|
*/
|
|
738
|
-
export function
|
|
739
|
-
// Pattern: "## Active Memory (
|
|
735
|
+
export function strippalaiaInjectedContext(text: string): string {
|
|
736
|
+
// Pattern: "## Active Memory (palaia)" ... "[palaia] auto-capture=on..." + optional trailing newlines
|
|
740
737
|
// The nudge line is always present and marks the end of the injected block
|
|
741
|
-
const PALAIA_BLOCK_RE = /## Active Memory \(
|
|
738
|
+
const PALAIA_BLOCK_RE = /## Active Memory \(palaia\)[\s\S]*?\[palaia\][^\n]*\n*/;
|
|
742
739
|
// Also strip Session Briefing blocks
|
|
743
|
-
const BRIEFING_BLOCK_RE = /## Session Briefing \(
|
|
740
|
+
const BRIEFING_BLOCK_RE = /## Session Briefing \(palaia\)[\s\S]*?(?=\n##|\n\n\n|$)/;
|
|
744
741
|
return text
|
|
745
742
|
.replace(PALAIA_BLOCK_RE, '')
|
|
746
743
|
.replace(BRIEFING_BLOCK_RE, '')
|
package/src/hooks/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Lifecycle hooks for the
|
|
2
|
+
* Lifecycle hooks for the palaia OpenClaw plugin.
|
|
3
3
|
*
|
|
4
4
|
* - before_prompt_build: Query-based contextual recall (Issue #65).
|
|
5
5
|
* Returns appendSystemContext with brain instruction when memory is used.
|
|
@@ -81,7 +81,7 @@ export {
|
|
|
81
81
|
isNoiseContent,
|
|
82
82
|
shouldAttemptCapture,
|
|
83
83
|
extractSignificance,
|
|
84
|
-
|
|
84
|
+
strippalaiaInjectedContext,
|
|
85
85
|
} from "./capture.js";
|
|
86
86
|
|
|
87
87
|
// Reaction exports
|
|
@@ -130,7 +130,7 @@ import {
|
|
|
130
130
|
resolveCaptureModel,
|
|
131
131
|
shouldAttemptCapture,
|
|
132
132
|
extractSignificance,
|
|
133
|
-
|
|
133
|
+
strippalaiaInjectedContext,
|
|
134
134
|
trimToRecentExchanges,
|
|
135
135
|
setLogger as setCaptureLogger,
|
|
136
136
|
getLlmImportFailureLogged,
|
|
@@ -278,7 +278,7 @@ export function registerHooks(api: OpenClawPluginApi, config: PalaiaPluginConfig
|
|
|
278
278
|
// ── /palaia status command ─────────────────────────────────────
|
|
279
279
|
api.registerCommand({
|
|
280
280
|
name: "palaia-status",
|
|
281
|
-
description: "Show
|
|
281
|
+
description: "Show palaia memory status",
|
|
282
282
|
async handler(_args: string) {
|
|
283
283
|
try {
|
|
284
284
|
const state = await loadPluginState(config.workspace);
|
|
@@ -293,7 +293,7 @@ export function registerHooks(api: OpenClawPluginApi, config: PalaiaPluginConfig
|
|
|
293
293
|
|
|
294
294
|
return { text: formatStatusResponse(state, stats, config) };
|
|
295
295
|
} catch (error) {
|
|
296
|
-
return { text: `
|
|
296
|
+
return { text: `palaia status error: ${error}` };
|
|
297
297
|
}
|
|
298
298
|
},
|
|
299
299
|
});
|
|
@@ -482,13 +482,13 @@ export function registerHooks(api: OpenClawPluginApi, config: PalaiaPluginConfig
|
|
|
482
482
|
}
|
|
483
483
|
|
|
484
484
|
// Apply type-weighted reranking and blocked filtering (Issue #121)
|
|
485
|
-
const rankedRaw = rerankByTypeWeight(entries, resolvedPrio.recallTypeWeight, config.recallRecencyBoost);
|
|
485
|
+
const rankedRaw = rerankByTypeWeight(entries, resolvedPrio.recallTypeWeight, config.recallRecencyBoost, config.manualEntryBoost);
|
|
486
486
|
const ranked = filterBlocked(rankedRaw, resolvedPrio.blocked);
|
|
487
487
|
|
|
488
488
|
// Build context string with char budget
|
|
489
489
|
// Progressive disclosure: compact mode for large stores (title + first line + ID)
|
|
490
490
|
const compact = shouldUseCompactMode(ranked.length);
|
|
491
|
-
let text = "## Active Memory (
|
|
491
|
+
let text = "## Active Memory (palaia)\n\n";
|
|
492
492
|
if (compact) {
|
|
493
493
|
text += "_Compact mode — use `memory_get <id>` for full details._\n\n";
|
|
494
494
|
}
|
|
@@ -512,7 +512,7 @@ export function registerHooks(api: OpenClawPluginApi, config: PalaiaPluginConfig
|
|
|
512
512
|
}
|
|
513
513
|
const { nudges } = checkNudges(pluginState);
|
|
514
514
|
if (nudges.length > 0) {
|
|
515
|
-
nudgeContext = "\n\n## Agent Nudge (
|
|
515
|
+
nudgeContext = "\n\n## Agent Nudge (palaia)\n\n" + nudges.join("\n\n");
|
|
516
516
|
}
|
|
517
517
|
await savePluginState(pluginState, resolved.workspace);
|
|
518
518
|
} catch {
|
|
@@ -556,7 +556,7 @@ export function registerHooks(api: OpenClawPluginApi, config: PalaiaPluginConfig
|
|
|
556
556
|
return {
|
|
557
557
|
prependContext: briefingText + text,
|
|
558
558
|
appendSystemContext: config.showMemorySources
|
|
559
|
-
? "You used
|
|
559
|
+
? "You used palaia memory in this turn. Add \u{1f9e0} at the very end of your response (after everything else, on its own line)."
|
|
560
560
|
: undefined,
|
|
561
561
|
};
|
|
562
562
|
} catch (error) {
|
|
@@ -587,6 +587,7 @@ export function registerHooks(api: OpenClawPluginApi, config: PalaiaPluginConfig
|
|
|
587
587
|
const hookOpts = buildRunnerOpts(config, { workspace: resolved.workspace });
|
|
588
588
|
|
|
589
589
|
if (!event.success || !event.messages || event.messages.length === 0) {
|
|
590
|
+
logger.info(`[palaia] Auto-capture skipped: success=${event.success}, messages=${event.messages?.length ?? 0}`);
|
|
590
591
|
return;
|
|
591
592
|
}
|
|
592
593
|
|
|
@@ -597,6 +598,7 @@ export function registerHooks(api: OpenClawPluginApi, config: PalaiaPluginConfig
|
|
|
597
598
|
|
|
598
599
|
const userTurns = allTexts.filter((t) => t.role === "user").length;
|
|
599
600
|
if (userTurns < config.captureMinTurns) {
|
|
601
|
+
logger.info(`[palaia] Auto-capture skipped: ${userTurns} user turns < captureMinTurns=${config.captureMinTurns}`);
|
|
600
602
|
return;
|
|
601
603
|
}
|
|
602
604
|
|
|
@@ -607,7 +609,7 @@ export function registerHooks(api: OpenClawPluginApi, config: PalaiaPluginConfig
|
|
|
607
609
|
collectedHints.push(...hints);
|
|
608
610
|
}
|
|
609
611
|
|
|
610
|
-
// Strip
|
|
612
|
+
// Strip palaia-injected recall context and private blocks from messages.
|
|
611
613
|
// The recall block is prepended to user messages by before_prompt_build.
|
|
612
614
|
// Without stripping, auto-capture would re-capture previously recalled memories.
|
|
613
615
|
// Private blocks (<private>...</private>) must be excluded from capture.
|
|
@@ -615,7 +617,7 @@ export function registerHooks(api: OpenClawPluginApi, config: PalaiaPluginConfig
|
|
|
615
617
|
const cleanedTexts = allTexts.map(t => ({
|
|
616
618
|
...t,
|
|
617
619
|
text: stripPrivateBlocks(
|
|
618
|
-
t.role === "user" ?
|
|
620
|
+
t.role === "user" ? strippalaiaInjectedContext(t.text) : t.text
|
|
619
621
|
),
|
|
620
622
|
}));
|
|
621
623
|
|
|
@@ -632,6 +634,7 @@ export function registerHooks(api: OpenClawPluginApi, config: PalaiaPluginConfig
|
|
|
632
634
|
const exchangeText = exchangeParts.join("\n");
|
|
633
635
|
|
|
634
636
|
if (!shouldAttemptCapture(exchangeText)) {
|
|
637
|
+
logger.info(`[palaia] Auto-capture skipped: content did not pass significance filter (${exchangeText.length} chars)`);
|
|
635
638
|
return;
|
|
636
639
|
}
|
|
637
640
|
|
|
@@ -733,6 +736,7 @@ export function registerHooks(api: OpenClawPluginApi, config: PalaiaPluginConfig
|
|
|
733
736
|
try {
|
|
734
737
|
const results = await extractWithLLM(event.messages, api.config, {
|
|
735
738
|
captureModel: config.captureModel,
|
|
739
|
+
workspace: resolved.workspace,
|
|
736
740
|
}, knownProjects);
|
|
737
741
|
|
|
738
742
|
await storeLLMResults(results);
|
|
@@ -752,6 +756,7 @@ export function registerHooks(api: OpenClawPluginApi, config: PalaiaPluginConfig
|
|
|
752
756
|
// Retry without captureModel -> resolveCaptureModel will use primary model
|
|
753
757
|
const fallbackResults = await extractWithLLM(event.messages, api.config, {
|
|
754
758
|
captureModel: undefined,
|
|
759
|
+
workspace: resolved.workspace,
|
|
755
760
|
}, knownProjects);
|
|
756
761
|
await storeLLMResults(fallbackResults);
|
|
757
762
|
llmHandled = true;
|
|
@@ -776,6 +781,7 @@ export function registerHooks(api: OpenClawPluginApi, config: PalaiaPluginConfig
|
|
|
776
781
|
if (config.captureFrequency === "significant") {
|
|
777
782
|
const significance = extractSignificance(exchangeText);
|
|
778
783
|
if (!significance) {
|
|
784
|
+
logger.info("[palaia] Auto-capture skipped: rule-based extraction found no significance (need ≥2 distinct tags)");
|
|
779
785
|
return;
|
|
780
786
|
}
|
|
781
787
|
captureData = significance;
|
package/src/hooks/recall.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type { RecallTypeWeights } from "../config.js";
|
|
9
|
-
import {
|
|
9
|
+
import { strippalaiaInjectedContext } from "./capture.js";
|
|
10
10
|
|
|
11
11
|
// ============================================================================
|
|
12
12
|
// Types
|
|
@@ -79,7 +79,7 @@ export function buildFootnote(
|
|
|
79
79
|
const dateStr = formatShortDate(e.date);
|
|
80
80
|
return dateStr ? `"${e.title}" (${dateStr})` : `"${e.title}"`;
|
|
81
81
|
});
|
|
82
|
-
return `\n\n\u{1f4ce}
|
|
82
|
+
return `\n\n\u{1f4ce} palaia: ${parts.join(", ")}`;
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
// Re-export formatShortDate from state for use here
|
|
@@ -96,7 +96,7 @@ const TRANSPARENCY_RECALL_THRESHOLD = 50;
|
|
|
96
96
|
const TRANSPARENCY_DAYS_THRESHOLD = 7;
|
|
97
97
|
|
|
98
98
|
const SATISFACTION_NUDGE_TEXT =
|
|
99
|
-
"Your user has been using
|
|
99
|
+
"Your user has been using palaia for a while now. " +
|
|
100
100
|
"Ask them casually if they're happy with the memory system. " +
|
|
101
101
|
"If there are issues, suggest `palaia doctor`.";
|
|
102
102
|
|
|
@@ -268,7 +268,7 @@ function isSystemOnlyContent(text: string): boolean {
|
|
|
268
268
|
export function buildRecallQuery(messages: unknown[]): string {
|
|
269
269
|
const texts = extractMessageTexts(messages).map(t =>
|
|
270
270
|
t.role === "user"
|
|
271
|
-
? { ...t, text:
|
|
271
|
+
? { ...t, text: strippalaiaInjectedContext(t.text) }
|
|
272
272
|
: t
|
|
273
273
|
);
|
|
274
274
|
|
|
@@ -367,12 +367,17 @@ export function rerankByTypeWeight(
|
|
|
367
367
|
results: QueryResult["results"],
|
|
368
368
|
weights: Record<string, number>,
|
|
369
369
|
recencyBoost = 0,
|
|
370
|
+
manualEntryBoost = 1.3,
|
|
370
371
|
): RankedEntry[] {
|
|
371
372
|
return results
|
|
372
373
|
.map((r) => {
|
|
373
374
|
const type = r.type || "memory";
|
|
374
375
|
const weight = weights[type] ?? 1.0;
|
|
375
376
|
const recency = calcRecencyBoost(r.created, recencyBoost);
|
|
377
|
+
// Manual entries (no auto-capture tag) get a boost over auto-captured ones.
|
|
378
|
+
// This ensures intentionally stored knowledge ranks higher than conversation noise.
|
|
379
|
+
const isAutoCapture = r.tags?.includes("auto-capture") ?? false;
|
|
380
|
+
const sourceBoost = isAutoCapture ? 1.0 : manualEntryBoost;
|
|
376
381
|
return {
|
|
377
382
|
id: r.id,
|
|
378
383
|
body: r.content || r.body || "",
|
|
@@ -383,7 +388,7 @@ export function rerankByTypeWeight(
|
|
|
383
388
|
score: r.score,
|
|
384
389
|
bm25Score: r.bm25_score,
|
|
385
390
|
embedScore: r.embed_score,
|
|
386
|
-
weightedScore: r.score * weight * recency,
|
|
391
|
+
weightedScore: r.score * weight * recency * sourceBoost,
|
|
387
392
|
created: r.created,
|
|
388
393
|
tags: r.tags,
|
|
389
394
|
};
|
package/src/hooks/session.ts
CHANGED
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
type ToolObservation,
|
|
21
21
|
} from "./state.js";
|
|
22
22
|
import {
|
|
23
|
-
|
|
23
|
+
strippalaiaInjectedContext,
|
|
24
24
|
stripPrivateBlocks,
|
|
25
25
|
trimToRecentExchanges,
|
|
26
26
|
extractWithLLM,
|
|
@@ -111,7 +111,7 @@ export function formatBriefing(briefing: PendingBriefing, maxChars: number): str
|
|
|
111
111
|
if (maxChars <= 0) return "";
|
|
112
112
|
if (!briefing.summary && briefing.openTasks.length === 0) return "";
|
|
113
113
|
|
|
114
|
-
const parts: string[] = ["## Session Briefing (
|
|
114
|
+
const parts: string[] = ["## Session Briefing (palaia)\n"];
|
|
115
115
|
|
|
116
116
|
if (briefing.summary) {
|
|
117
117
|
const agoMs = Date.now() - briefing.timestamp;
|
|
@@ -161,6 +161,7 @@ export async function captureSessionSummary(
|
|
|
161
161
|
try {
|
|
162
162
|
const results = await extractWithLLM(messages, api.config, {
|
|
163
163
|
captureModel: config.captureModel,
|
|
164
|
+
workspace: config.workspace,
|
|
164
165
|
}, []);
|
|
165
166
|
|
|
166
167
|
if (results.length > 0) {
|
|
@@ -176,7 +177,7 @@ export async function captureSessionSummary(
|
|
|
176
177
|
const cleaned = allTexts.map((t: { role: string; text: string }) => ({
|
|
177
178
|
...t,
|
|
178
179
|
text: stripPrivateBlocks(
|
|
179
|
-
t.role === "user" ?
|
|
180
|
+
t.role === "user" ? strippalaiaInjectedContext(t.text) : t.text
|
|
180
181
|
),
|
|
181
182
|
}));
|
|
182
183
|
const recent = trimToRecentExchanges(cleaned, 3);
|
package/src/hooks/state.ts
CHANGED
|
@@ -338,10 +338,11 @@ const VALID_SCOPES = ["private", "team", "public"];
|
|
|
338
338
|
|
|
339
339
|
/**
|
|
340
340
|
* Check if a scope string is valid for palaia write.
|
|
341
|
-
* Valid: "private", "team", "public"
|
|
341
|
+
* Valid: "private", "team", "public".
|
|
342
|
+
* Legacy shared:X is accepted but normalized to "team" by the CLI.
|
|
342
343
|
*/
|
|
343
344
|
export function isValidScope(s: string): boolean {
|
|
344
|
-
return VALID_SCOPES.includes(s)
|
|
345
|
+
return VALID_SCOPES.includes(s);
|
|
345
346
|
}
|
|
346
347
|
|
|
347
348
|
/**
|
|
@@ -395,7 +396,7 @@ export function formatStatusResponse(
|
|
|
395
396
|
stats: Record<string, unknown>,
|
|
396
397
|
config: PalaiaPluginConfig,
|
|
397
398
|
): string {
|
|
398
|
-
const lines: string[] = ["
|
|
399
|
+
const lines: string[] = ["palaia Memory Status", ""];
|
|
399
400
|
|
|
400
401
|
// Recall count
|
|
401
402
|
const sinceDate = state.firstRecallTimestamp
|
package/src/runner.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* palaia CLI subprocess runner.
|
|
3
3
|
*
|
|
4
4
|
* Executes palaia CLI commands, parses JSON output, handles binary detection
|
|
5
5
|
* and timeouts. This is the bridge between the OpenClaw plugin and the
|
|
@@ -92,7 +92,7 @@ export async function detectBinary(
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
throw new Error(
|
|
95
|
-
"
|
|
95
|
+
"palaia binary not found. Install with: pip install palaia\n" +
|
|
96
96
|
"Or set binaryPath in plugin config."
|
|
97
97
|
);
|
|
98
98
|
}
|
|
@@ -129,7 +129,7 @@ function execCommand(
|
|
|
129
129
|
(error, stdout, stderr) => {
|
|
130
130
|
if (error && (error as any).killed) {
|
|
131
131
|
reject(
|
|
132
|
-
new Error(`
|
|
132
|
+
new Error(`palaia command timed out after ${timeout}ms: ${cmd} ${args.join(" ")}`)
|
|
133
133
|
);
|
|
134
134
|
return;
|
|
135
135
|
}
|
|
@@ -184,7 +184,7 @@ export async function run(
|
|
|
184
184
|
|
|
185
185
|
if (result.exitCode !== 0) {
|
|
186
186
|
const errMsg = result.stderr.trim() || result.stdout.trim();
|
|
187
|
-
throw new Error(`
|
|
187
|
+
throw new Error(`palaia CLI error (exit ${result.exitCode}): ${errMsg}`);
|
|
188
188
|
}
|
|
189
189
|
|
|
190
190
|
return result.stdout;
|
package/src/tools.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Agent tools: memory_search, memory_get, memory_write.
|
|
3
3
|
*
|
|
4
|
-
* These tools are the core of the
|
|
4
|
+
* These tools are the core of the palaia OpenClaw integration.
|
|
5
5
|
* They shell out to the palaia CLI with --json and return results
|
|
6
6
|
* in the format OpenClaw agents expect.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { Type } from "@sinclair/typebox";
|
|
10
|
-
import { run, runJson, type RunnerOpts } from "./runner.js";
|
|
10
|
+
import { run, runJson, getEmbedServerManager, type RunnerOpts } from "./runner.js";
|
|
11
11
|
import type { PalaiaPluginConfig } from "./config.js";
|
|
12
12
|
import { sanitizeScope, isValidScope } from "./hooks/index.js";
|
|
13
13
|
import { loadPriorities, resolvePriorities } from "./priorities.js";
|
|
@@ -62,7 +62,7 @@ function buildRunnerOpts(config: PalaiaPluginConfig): RunnerOpts {
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
/**
|
|
65
|
-
* Register all
|
|
65
|
+
* Register all palaia agent tools on the given plugin API.
|
|
66
66
|
*/
|
|
67
67
|
export function registerTools(api: OpenClawPluginApi, config: PalaiaPluginConfig): void {
|
|
68
68
|
const opts = buildRunnerOpts(config);
|
|
@@ -71,7 +71,7 @@ export function registerTools(api: OpenClawPluginApi, config: PalaiaPluginConfig
|
|
|
71
71
|
api.registerTool({
|
|
72
72
|
name: "memory_search",
|
|
73
73
|
description:
|
|
74
|
-
"Semantically search
|
|
74
|
+
"Semantically search palaia memory for relevant notes and context.",
|
|
75
75
|
parameters: Type.Object({
|
|
76
76
|
query: Type.String({ description: "Search query" }),
|
|
77
77
|
maxResults: Type.Optional(
|
|
@@ -114,30 +114,48 @@ export function registerTools(api: OpenClawPluginApi, config: PalaiaPluginConfig
|
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
const limit = params.maxResults || config.maxResults || 5;
|
|
117
|
-
const
|
|
117
|
+
const includeCold = params.tier === "all" || config.tier === "all";
|
|
118
118
|
|
|
119
|
-
//
|
|
120
|
-
|
|
121
|
-
|
|
119
|
+
// Try embed server first — its queue correctly handles concurrent requests
|
|
120
|
+
// without counting wait time against the timeout.
|
|
121
|
+
let result: QueryResult | null = null;
|
|
122
|
+
if (config.embeddingServer) {
|
|
123
|
+
try {
|
|
124
|
+
const mgr = getEmbedServerManager(opts);
|
|
125
|
+
const resp = await mgr.query({
|
|
126
|
+
text: params.query,
|
|
127
|
+
top_k: limit,
|
|
128
|
+
include_cold: includeCold,
|
|
129
|
+
...(params.type ? { type: params.type } : {}),
|
|
130
|
+
}, config.timeoutMs || 3000);
|
|
131
|
+
if (resp?.result?.results && Array.isArray(resp.result.results)) {
|
|
132
|
+
result = { results: resp.result.results };
|
|
133
|
+
}
|
|
134
|
+
} catch {
|
|
135
|
+
// Fall through to CLI
|
|
136
|
+
}
|
|
122
137
|
}
|
|
123
138
|
|
|
124
|
-
//
|
|
125
|
-
if (
|
|
126
|
-
args.
|
|
139
|
+
// CLI fallback — longer timeout since it includes process spawn overhead
|
|
140
|
+
if (!result) {
|
|
141
|
+
const args: string[] = ["query", params.query, "--limit", String(limit)];
|
|
142
|
+
if (includeCold) {
|
|
143
|
+
args.push("--all");
|
|
144
|
+
}
|
|
145
|
+
if (params.type) {
|
|
146
|
+
args.push("--type", params.type);
|
|
147
|
+
}
|
|
148
|
+
result = await runJson<QueryResult>(args, { ...opts, timeoutMs: 15000 });
|
|
127
149
|
}
|
|
128
150
|
|
|
129
|
-
const result = await runJson<QueryResult>(args, opts);
|
|
130
|
-
|
|
131
151
|
// Apply scope visibility filter (Issue #145: agent isolation)
|
|
132
152
|
let filteredResults = result.results || [];
|
|
133
153
|
if (scopeVisibility) {
|
|
134
154
|
filteredResults = filteredResults.filter((r) => {
|
|
135
155
|
const scope = r.scope || "team";
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
return false;
|
|
140
|
-
});
|
|
156
|
+
// Legacy shared:X entries are treated as team
|
|
157
|
+
const effectiveScope = scope.startsWith("shared:") ? "team" : scope;
|
|
158
|
+
return scopeVisibility!.includes(effectiveScope);
|
|
141
159
|
});
|
|
142
160
|
}
|
|
143
161
|
|
|
@@ -173,7 +191,7 @@ export function registerTools(api: OpenClawPluginApi, config: PalaiaPluginConfig
|
|
|
173
191
|
// ── memory_get ─────────────────────────────────────────────────
|
|
174
192
|
api.registerTool({
|
|
175
193
|
name: "memory_get",
|
|
176
|
-
description: "Read a specific
|
|
194
|
+
description: "Read a specific palaia memory entry by path or id.",
|
|
177
195
|
parameters: Type.Object({
|
|
178
196
|
path: Type.String({ description: "Memory path or UUID" }),
|
|
179
197
|
from: Type.Optional(
|
|
@@ -213,12 +231,12 @@ export function registerTools(api: OpenClawPluginApi, config: PalaiaPluginConfig
|
|
|
213
231
|
{
|
|
214
232
|
name: "memory_write",
|
|
215
233
|
description:
|
|
216
|
-
"Write a new memory entry to
|
|
234
|
+
"Write a new memory entry to palaia. WAL-backed, crash-safe.",
|
|
217
235
|
parameters: Type.Object({
|
|
218
236
|
content: Type.String({ description: "Memory content to write" }),
|
|
219
237
|
scope: Type.Optional(
|
|
220
238
|
Type.String({
|
|
221
|
-
description: "Scope: private|team|
|
|
239
|
+
description: "Scope: private|team|public (default: team)",
|
|
222
240
|
default: "team",
|
|
223
241
|
})
|
|
224
242
|
),
|
|
@@ -306,7 +324,7 @@ export function registerTools(api: OpenClawPluginApi, config: PalaiaPluginConfig
|
|
|
306
324
|
content: [
|
|
307
325
|
{
|
|
308
326
|
type: "text" as const,
|
|
309
|
-
text: `Invalid scope "${params.scope}". Valid scopes: private, team, public
|
|
327
|
+
text: `Invalid scope "${params.scope}". Valid scopes: private, team, public`,
|
|
310
328
|
},
|
|
311
329
|
],
|
|
312
330
|
};
|
package/src/types.ts
CHANGED
|
@@ -324,7 +324,7 @@ export type SubagentEndReason = "deleted" | "completed" | "swept" | "released";
|
|
|
324
324
|
/**
|
|
325
325
|
* ContextEngine interface — matches OpenClaw v2026.3.28.
|
|
326
326
|
*
|
|
327
|
-
* This is the full interface.
|
|
327
|
+
* This is the full interface. palaia implements a subset;
|
|
328
328
|
* optional methods are marked with `?`.
|
|
329
329
|
*/
|
|
330
330
|
export interface ContextEngine {
|