mirror of
https://github.com/dtzp555-max/memory-continuity.git
synced 2026-07-22 05:25:07 +00:00
feat: add memory file count hard limit (500) and archive cap (20)
Prevents memory/ from growing unbounded by capping STATE_ARCHIVE files at 20 per type and enforcing a 500-file hard limit on the entire memory directory. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -43,11 +43,9 @@
|
|||||||
- Required sequence before any gateway intervention: (1) run `openclaw gateway status`; (2) report findings to Tao; (3) prefer non-disruptive diagnosis first; (4) only touch gateway lifecycle if Tao explicitly approves the exact command.
|
- Required sequence before any gateway intervention: (1) run `openclaw gateway status`; (2) report findings to Tao; (3) prefer non-disruptive diagnosis first; (4) only touch gateway lifecycle if Tao explicitly approves the exact command.
|
||||||
|
|
||||||
## 7) Model policy (current preference)
|
## 7) Model policy (current preference)
|
||||||
- Historical temporary preference once was: all subagents **primary** = `openai-codex/gpt-5.2`, **fallback** = `github-copilot/claude-opus-4.6`.
|
- Current config (2026-03-21): all agents **primary** = `claude-local/claude-sonnet-4-6`, **fallback** = `openai-codex/gpt-5.4` (only one fallback; Minimax removed).
|
||||||
- Current important exception / newer rule: **execution agents** (esp. `codex_worker`, and by default other workers unless Tao says otherwise) must target **primary** = `openai-codex/gpt-5.4`.
|
- **铁律:Fallback 发生时必须通知 Tao。** 适用于**所有 agent**(main、tech_geek、travel_assistant、finance_assistant、所有 execution agents)。无论哪条链路触发了 fallback,都必须立即通知 Tao,说明:哪个 agent、从什么 model fallback 到了什么 model、原因(timeout / error / unavailable)。不允许静默 fallback。
|
||||||
- **No silent fallback:** if the system falls back to any other model (or if `openai-codex/gpt-5.4` becomes unknown/unavailable), main must **immediately notify Tao**.
|
- If an agent is not responding, main must consider **model unavailability/fallback** as a first-class suspected cause and **tell Tao**.
|
||||||
- **No auto-fallback for execution agents:** if 5.4 is unavailable, workers should not continue on another model; main should report and wait for Tao’s model decision.
|
|
||||||
- If an execution agent is not responding, main must consider **model unavailability/fallback** as a first-class suspected cause and **tell Tao**.
|
|
||||||
- Additional long-lived execution agents initialized locally for repeat use: **docs_worker**, **qa_worker**, **ops_worker**.
|
- Additional long-lived execution agents initialized locally for repeat use: **docs_worker**, **qa_worker**, **ops_worker**.
|
||||||
- Execution-agent system needs a standardized dispatch/handoff layer: task input template, result format, blocker/escalation rules, and a **main-to-Tao forwarding rule**.
|
- Execution-agent system needs a standardized dispatch/handoff layer: task input template, result format, blocker/escalation rules, and a **main-to-Tao forwarding rule**.
|
||||||
- **Hard reporting/forwarding protocol (must-follow):**
|
- **Hard reporting/forwarding protocol (must-follow):**
|
||||||
@@ -72,3 +70,9 @@
|
|||||||
- Local memory dir: `/Users/taodeng/.openclaw/workspace/main/memory/`.
|
- Local memory dir: `/Users/taodeng/.openclaw/workspace/main/memory/`.
|
||||||
- Added `memory/INDEX.md` to classify daily notes vs topic notes vs automation state JSON (do not rename/move state JSON without updating jobs).
|
- Added `memory/INDEX.md` to classify daily notes vs topic notes vs automation state JSON (do not rename/move state JSON without updating jobs).
|
||||||
- Plan: create a separate GitHub KB repo (option A) for shareable/curated knowledge; keep private/sensitive items local and only publish redacted content.
|
- Plan: create a separate GitHub KB repo (option A) for shareable/curated knowledge; keep private/sensitive items local and only publish redacted content.
|
||||||
|
|
||||||
|
## 11) Oracle Cloud
|
||||||
|
- See [memory/oracle_cloud.md](memory/oracle_cloud.md) for SSH connection details and deployment notes.
|
||||||
|
|
||||||
|
## 12) 开发原则 (Dev Principles)
|
||||||
|
- See [memory/dev_principles.md](memory/dev_principles.md) for Tao's principles on new project development (when to reuse vs build, code quality, testing, versioning, etc.). Read before starting any new project.
|
||||||
|
|||||||
+18
-20
@@ -1,33 +1,31 @@
|
|||||||
# Current State
|
# Current State
|
||||||
> Last updated: 2026-03-13T07:02:00+10:00
|
> Last updated: 2026-03-21T02:00:58.027Z
|
||||||
|
|
||||||
## Objective
|
## Objective
|
||||||
Validate the memory-continuity lifecycle plugin path safely after upgrade.
|
但是文件数目不可以超过500条
|
||||||
|
|
||||||
## Current Step
|
## Current Step
|
||||||
Startup-injection testing is confirmed on multiple resident subagents after the upgrade; Discord main/channel/thread paths have now been tested and are not currently supported for reliable continuity recovery; compaction-path testing is now being prepared on `travel_assistant` as the disposable session.
|
Conversation ended after 181 messages
|
||||||
|
|
||||||
## Key Decisions
|
## Key Decisions
|
||||||
- Keep `SKILL.md + standard lifecycle plugin` as the primary architecture.
|
- Auto-extracted from conversation
|
||||||
- Treat ContextEngine as a future option, not the v1 default.
|
|
||||||
- Do not force compaction on the main live session just to test Experiment C.
|
|
||||||
- Use disposable resident subagent sessions for future compaction-path testing.
|
|
||||||
- `travel_assistant` is now a valid sample again after removing the old wrong workspace and fixing its `AGENTS.md` startup rule to read `memory/CURRENT_STATE.md`.
|
|
||||||
- Alpha support boundary is now explicit: resident subagent startup continuity is supported; Discord main/channel/thread continuity is not yet supported in `v0.3.0-probe`.
|
|
||||||
|
|
||||||
## Next Action
|
## Next Action
|
||||||
Two-track next step:
|
Continue from where we left off
|
||||||
1. If continuing active testing, use `travel_assistant` as the sacrificial session, reload the updated probe logging, then force/accelerate a real compaction event and compare hook behavior against the failure chain seen on main.
|
|
||||||
2. If pausing for upstream uncertainty, keep the fallback-continuity GitHub issue open and wait for OpenClaw feedback before spending more effort on the main-session fallback path.
|
|
||||||
|
|
||||||
## Blockers
|
## Blockers
|
||||||
- Experiment C still lacks a real compaction event, so compaction-hook behavior remains unproven.
|
None
|
||||||
- New upstream-suspected blocker: fallback continuity failure may be caused by OpenClaw providing incomplete plugin runtime (`workspaceDir=<missing>`) on fallback/new-session recovery path.
|
|
||||||
|
|
||||||
## Unsurfaced Results
|
## Unsurfaced Results
|
||||||
- Experiment A is now confirmed on multiple resident subagents.
|
🦞 大内总管: 已加硬上限。
|
||||||
- `tech_geek` recovered plugin-injected startup continuity state without using `read`.
|
|
||||||
- `travel_assistant` also recovered the correct upgraded smoke-test state after fixing its old workspace pollution and startup rule.
|
**改动:**
|
||||||
- Fresh Discord main/channel/thread tests failed to preserve short facts or concrete working-state recovery across new sessions; treat Discord main continuity as unsupported for this alpha.
|
- `MAX_MEMORY_FILES = 500`
|
||||||
- New execution-model finding: main can successfully dispatch and consume results from temporary subagents (single, follow-up, and parallel two-worker tests all succeeded).
|
- 每次 archive 后检查 `memory/` 总文件数
|
||||||
- New ACP finding: Codex ACP worker is validated as a usable execution worker for main; Claude ACP today remains an invalid sample because Tao confirmed Claude was already in timeout/overuse state, so do not classify that failure as product/runtime evidence.
|
- 超过 500 → 自动删最旧的可删文件,降到 450(留 50 个余量)
|
||||||
|
- **不会删的**:`CURRENT_STATE.md`、`MEMORY.md`、`INDEX.md`、主题文件、JSON 状态文件
|
||||||
|
|
||||||
|
**三层防护:**
|
||||||
|
1. 单类 archive 上限 20 个
|
||||||
|
2. 总文件数硬上限 500
|
||||||
|
3. 清理时按修改时间排序,从最旧的开始删
|
||||||
|
|||||||
@@ -130,6 +130,22 @@ Update the file by **overwriting** it, not appending, at these moments:
|
|||||||
| Before handoff / subagent exit | Preserves outputs and unsurfaced results |
|
| Before handoff / subagent exit | Preserves outputs and unsurfaced results |
|
||||||
| After a substantive state change | Keeps checkpoint aligned with actual work |
|
| After a substantive state change | Keeps checkpoint aligned with actual work |
|
||||||
|
|
||||||
|
### Override rule
|
||||||
|
|
||||||
|
**CURRENT_STATE.md must always be overwritten when:**
|
||||||
|
- A new task or objective starts — regardless of what is currently in the file
|
||||||
|
- The previous objective is complete or abandoned
|
||||||
|
- The user gives a new task that supersedes the previous one
|
||||||
|
|
||||||
|
Having content in CURRENT_STATE.md does NOT mean it should be preserved.
|
||||||
|
Content only matters if Objective is still active and work is genuinely in progress.
|
||||||
|
|
||||||
|
Checking before overwrite:
|
||||||
|
- Read the file
|
||||||
|
- If Objective matches the current task → update in place (overwrite)
|
||||||
|
- If Objective does NOT match → overwrite the entire file with the new state
|
||||||
|
- Never append. Never skip the update because "there's already something there".
|
||||||
|
|
||||||
### 4. Keep the checkpoint small
|
### 4. Keep the checkpoint small
|
||||||
|
|
||||||
`CURRENT_STATE.md` should usually stay under about 40 lines and be readable in
|
`CURRENT_STATE.md` should usually stay under about 40 lines and be readable in
|
||||||
|
|||||||
@@ -1,6 +1,18 @@
|
|||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
|
|
||||||
|
const MAX_ARCHIVE_COUNT = 20;
|
||||||
|
const MAX_MEMORY_FILES = 500;
|
||||||
|
|
||||||
|
// Protected files that should never be auto-deleted
|
||||||
|
const PROTECTED_FILES = new Set([
|
||||||
|
"CURRENT_STATE.md",
|
||||||
|
"MEMORY.md",
|
||||||
|
"INDEX.md",
|
||||||
|
"dev_principles.md",
|
||||||
|
"oracle_cloud.md",
|
||||||
|
]);
|
||||||
|
|
||||||
const PLACEHOLDER_VALUES = new Set([
|
const PLACEHOLDER_VALUES = new Set([
|
||||||
"",
|
"",
|
||||||
"none",
|
"none",
|
||||||
@@ -82,11 +94,70 @@ function formatArchiveStamp(date = new Date()) {
|
|||||||
].join("-") + `_${pad(date.getHours())}-${pad(date.getMinutes())}`;
|
].join("-") + `_${pad(date.getHours())}-${pad(date.getMinutes())}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function pruneArchives(dir: string, pattern: RegExp, max: number) {
|
||||||
|
try {
|
||||||
|
const files = fs.readdirSync(dir)
|
||||||
|
.filter((f: string) => pattern.test(f))
|
||||||
|
.sort();
|
||||||
|
const excess = files.length - max;
|
||||||
|
if (excess > 0) {
|
||||||
|
for (let i = 0; i < excess; i++) {
|
||||||
|
fs.unlinkSync(path.join(dir, files[i]));
|
||||||
|
}
|
||||||
|
console.log(`[memory-continuity] pruned ${excess} old archive(s) in ${dir}`);
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
function enforceFileLimit(memoryDir: string) {
|
||||||
|
try {
|
||||||
|
const allFiles = fs.readdirSync(memoryDir, { recursive: true }) as string[];
|
||||||
|
// Only count files, not directories
|
||||||
|
const fileList = allFiles
|
||||||
|
.map((f: string) => ({ name: String(f), full: path.join(memoryDir, String(f)) }))
|
||||||
|
.filter((f) => {
|
||||||
|
try { return fs.statSync(f.full).isFile(); } catch { return false; }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (fileList.length <= MAX_MEMORY_FILES) return;
|
||||||
|
|
||||||
|
// Sort deletable files by mtime (oldest first), skip protected
|
||||||
|
const deletable = fileList
|
||||||
|
.filter((f) => {
|
||||||
|
const base = path.basename(f.name);
|
||||||
|
if (PROTECTED_FILES.has(base)) return false;
|
||||||
|
if (base.endsWith(".json")) return false; // automation state
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.map((f) => ({ ...f, mtime: fs.statSync(f.full).mtimeMs }))
|
||||||
|
.sort((a, b) => a.mtime - b.mtime);
|
||||||
|
|
||||||
|
const toDelete = fileList.length - 450; // bring down to 450 for headroom
|
||||||
|
const deleted = Math.min(toDelete, deletable.length);
|
||||||
|
for (let i = 0; i < deleted; i++) {
|
||||||
|
fs.unlinkSync(deletable[i].full);
|
||||||
|
}
|
||||||
|
if (deleted > 0) {
|
||||||
|
console.log(`[memory-continuity] enforced file limit: deleted ${deleted} oldest files (${fileList.length} → ${fileList.length - deleted})`);
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
function appendArchive(workspaceDir: string, markdown: string) {
|
function appendArchive(workspaceDir: string, markdown: string) {
|
||||||
const stamp = formatArchiveStamp();
|
const stamp = formatArchiveStamp();
|
||||||
const archiveDir = path.join(workspaceDir, "memory", "session_archive");
|
const archiveDir = path.join(workspaceDir, "memory", "session_archive");
|
||||||
fs.mkdirSync(archiveDir, { recursive: true });
|
fs.mkdirSync(archiveDir, { recursive: true });
|
||||||
fs.writeFileSync(path.join(archiveDir, `${stamp}.md`), markdown, "utf8");
|
fs.writeFileSync(path.join(archiveDir, `${stamp}.md`), markdown, "utf8");
|
||||||
|
|
||||||
|
// Prune session_archive/ beyond MAX_ARCHIVE_COUNT
|
||||||
|
pruneArchives(archiveDir, /\.md$/, MAX_ARCHIVE_COUNT);
|
||||||
|
|
||||||
|
// Also prune STATE_ARCHIVE_*.md in memory/ root (generated by OpenClaw native)
|
||||||
|
const memoryDir = path.join(workspaceDir, "memory");
|
||||||
|
pruneArchives(memoryDir, /^STATE_ARCHIVE_.*\.md$/, MAX_ARCHIVE_COUNT);
|
||||||
|
|
||||||
|
// Hard cap: total files in memory/ must not exceed MAX_MEMORY_FILES
|
||||||
|
enforceFileLimit(memoryDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function register(api: any) {
|
export default function register(api: any) {
|
||||||
|
|||||||
Reference in New Issue
Block a user