feat(memory): add nightly vault index rebuild and agent docs

Add maybeRebuildVaultIndex() to heartbeat service for nightly full index
rebuilds during quiet hours (2-5am). Uses a date stamp file to run at
most once per day. Update AGENT.md with memory vault conventions,
note format, and tool usage guidelines.
This commit is contained in:
Paul De Velder 2026-02-23 16:16:34 +01:00
parent 1102a64715
commit ac5b384f16
2 changed files with 70 additions and 2 deletions

View file

@ -17,6 +17,7 @@ import (
"github.com/sipeed/picoclaw/pkg/bus"
"github.com/sipeed/picoclaw/pkg/constants"
"github.com/sipeed/picoclaw/pkg/logger"
"github.com/sipeed/picoclaw/pkg/memory"
"github.com/sipeed/picoclaw/pkg/state"
"github.com/sipeed/picoclaw/pkg/tools"
)
@ -159,6 +160,9 @@ func (hs *HeartbeatService) executeHeartbeat() {
logger.DebugC("heartbeat", "Executing heartbeat")
// Nightly vault index rebuild (runs once per day between 2-5am)
hs.maybeRebuildVaultIndex()
prompt := hs.buildPrompt()
if prompt == "" {
logger.InfoC("heartbeat", "No heartbeat prompt (HEARTBEAT.md empty or missing)")
@ -341,6 +345,37 @@ func (hs *HeartbeatService) parseLastChannel(lastChannel string) (platform, user
return platform, userID
}
// maybeRebuildVaultIndex performs a nightly full rebuild of the memory vault
// index. It runs at most once per day, during quiet hours (2-5am local time),
// to catch any inconsistencies from incremental updates.
func (hs *HeartbeatService) maybeRebuildVaultIndex() {
memoryDir := filepath.Join(hs.workspace, "memory")
stampFile := filepath.Join(memoryDir, ".last_rebuild")
today := time.Now().Format("2006-01-02")
// Check if already rebuilt today
if data, err := os.ReadFile(stampFile); err == nil {
if strings.TrimSpace(string(data)) == today {
return
}
}
// Only rebuild during quiet hours (2am-5am)
hour := time.Now().Hour()
if hour < 2 || hour >= 5 {
return
}
vault := memory.NewVault(memoryDir)
if err := vault.RebuildIndex(); err != nil {
hs.logError("Vault index rebuild failed: %v", err)
return
}
os.WriteFile(stampFile, []byte(today), 0o644)
hs.logInfo("Nightly vault index rebuild completed")
}
// logInfo logs an informational message to the heartbeat log
func (hs *HeartbeatService) logInfo(format string, args ...any) {
hs.log("INFO", format, args...)

View file

@ -7,6 +7,39 @@ You are a helpful AI assistant. Be concise, accurate, and friendly.
- Always explain what you're doing before taking actions
- Ask for clarification when request is ambiguous
- Use tools to help accomplish tasks
- Remember important information in your memory files
- Remember important information using the memory vault (see below)
- Be proactive and helpful
- Learn from user feedback
- Learn from user feedback
## Memory Vault
Your memory is an Obsidian-compatible vault of markdown notes in `memory/`. Each note has YAML frontmatter for metadata.
### Note Format
```
---
title: Note Title Here
created: 2026-02-23
updated: 2026-02-23
tags: [tag1, tag2]
aliases: [alternate-name]
---
Content here. Link to other notes with [[note-title]].
```
### Tools
- **memory_save** — Save knowledge with structured frontmatter and tags. Auto-generates frontmatter, preserves created dates on updates, and rebuilds the vault index.
- **memory_search** — Search by tags (AND logic) or text query matching title/tags/aliases. Returns metadata; use memory_recall to read full content.
- **memory_recall** — Read full note content by exact path or by topic (searches and reads top matches).
### Conventions
- Folders are for human convenience (e.g. `topics/`, `people/`, `projects/`), not enforced by code
- Tags are the primary organizational mechanism
- Daily notes (`YYYYMM/YYYYMMDD.md`) are stored alongside vault notes
- The index at `_index.md` is auto-generated — never edit it manually
- When you learn something new about the user or a topic, save a note
- When answering questions, check memory first with memory_search or memory_recall