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
6 changes: 3 additions & 3 deletions .aiwg/reports/g-icm-01-interface-inventory.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
"control_id": "G-ICM-01",
"project_id": "github.com/chronodeai/agentmemory",
"source_identity": {
"commit_sha": "b244872f43e6c98363d864305f5fcd594d9d1e31",
"commit_tree_sha": "4a0008a80fb82dc38ad719f30b452c969b09acb6",
"inventory_input_sha256": "3e1807639308f135d8310debd30c1836fc8b33b25e377199752884e9b051645c"
"commit_sha": "e25ab1dd3884301b307a62f278cd38966fb043f9",
"commit_tree_sha": "70d799994621397b125eb394d3c9bad4be75bc5c",
"inventory_input_sha256": "7054c53f2295f18ea1edf6ad6930e8c66ab96c6d8a194bd856a8340a6576bed9"
},
"public_route_allowlist": [
"GET /agentmemory/livez"
Expand Down
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,24 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),

## [Unreleased]

## [0.9.30-chronode.6] — 2026-08-26

Checkup-and-ledger closure release: TTL-bounded `.prev` retention, watchdog RSS sampling, and tier-1 ledger closure of the sync-wave follow-ups.

### Added

- **Expired `.prev` reclamation before live-stream rotation** (`src/state/live-stream-rotation.ts`). Before renaming an oversized current stream onto `<path>.prev`, an existing generation whose mtime is older than `AGENTMEMORY_LIVE_STREAM_PREV_TTL_DAYS` days is unlinked best-effort — one warn line on failure — so its bytes are reclaimed while the oversized current file still exists. The TTL defaults to `30`; unparsable and negative values fall back to the default via `parseInt`, and `0` opts out entirely. Fresh generations stay untouched until the rename replaces them atomically. Tests cover expired-removed-before-rename, fresh-preserved, rotation with no `.prev` present, and the stuck-generation warn path.
- **Release-dir pruning rule** in the deploy recipe (`docs/upstream-sync.md` §5 step 8): after a fresh release soaks, keep the 3 newest dirs under `releases/` plus whatever `current` references, delete the rest — one directory per train had accumulated indefinitely (23 on disk before the rule).

### Deployment (not repo code)

- **Watchdog RSS sampling** (`~/.agentmemory/bin/watchdog.sh`): every run now appends `<ISO-8601 UTC> rss_kb=<kb>` for the PID in `~/.agentmemory/iii.pid` to `~/Library/Logs/Agentmemory/watchdog.log`, giving the restart history a memory-growth series alongside the viewer-down and engine-restarted lines.

### Ledger

- Six tier-1 `decisions` rows recorded under project `chronodeai/agentmemory`: the embedding-dimension premise closed as resolved (newest vector shard decodes to 2560-dim float32 ×145 samples, `.env` pins `OPENAI_EMBEDDING_DIMENSIONS=2560` against omlx `:8002`, which honors the Matryoshka `dimensions` param at 2560 and 1024); unbounded `.prev` retention superseded by this train's TTL fix; the release-pruning policy; the `/lesson` adoption gap recorded visibility-only (tool-driven pipeline, no auto-create upstream); the weekly `scripts/sync/upstream-status.sh` habit; and the auth-failure log pass.
- **Auth-failure log pass** (read-only): `legacy_authentication_disabled`, `401`, and `project_binding_mismatch` occur zero times across `~/Library/Logs/Agentmemory/*.log` and `/tmp/am-worker-9032348.log`, in both pre-sync (< 2026-08-24T21:02:26Z, activation of `0.9.30-chronode.1`) and post-sync buckets; only near-misses exist (legacy data-dir notices, one empty OTel WebSocket error line), with a retained-logs-only caveat in the ledger row.

## [0.9.30-chronode.5] — 2026-08-25

Backlog-closure release: live-stream disk bounding, deployment watchdog v2, and a written upstream-sync process.
Expand Down
2 changes: 1 addition & 1 deletion ci/r13-test-manifest.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"count": 180,
"sha256": "e8489acf3396ad149ca9342214e4865124021fd1e05d37ff548e63dcf2f6a242",
"content_sha256": "d814f7af2764ee51260a113cdce86f535b3bc7ca1074bafe1b9cdebc9bef7596"
"content_sha256": "1a221bd79fe25417ada1a4d3628eac17bc3c92eaf01b023aa41148dc7f4d4d25"
}
16 changes: 16 additions & 0 deletions docs/upstream-sync.md
Original file line number Diff line number Diff line change
Expand Up @@ -231,3 +231,19 @@ main sha.

7. Update the tier-1 decision record (`decisions` table via `dsh-sor.sh`) so
the deployment is queryable truth, not session folklore.

8. **Prune release dirs after soak.** Once the new release has soaked (health
endpoint and hook round-trip green), delete old release directories under
`releases/` keeping the 3 newest plus any directory the `current` symlink
references:

```sh
cd "$HOME/Library/Application Support/Agentmemory/releases" || exit 1
KEEP_CURRENT="$(basename "$(readlink ../current)")"
ls -t | grep -v -x -e "$KEEP_CURRENT" | tail -n +4 | while IFS= read -r dir; do
rm -rf "$dir"
done
```

Without this, one directory per train accumulates indefinitely (23 were
on disk before the rule existed).
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@agentmemory/agentmemory",
"version": "0.9.30-chronode.5",
"version": "0.9.30-chronode.6",
"description": "Persistent memory for AI coding agents, powered by iii-engine's three primitives",
"type": "module",
"main": "dist/index.mjs",
Expand Down
4 changes: 2 additions & 2 deletions packages/mcp/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@agentmemory/mcp",
"version": "0.9.30-chronode.5",
"version": "0.9.30-chronode.6",
"description": "Standalone MCP server for agentmemory — thin shim that re-exposes @agentmemory/agentmemory's MCP entrypoint",
"type": "module",
"bin": {
Expand Down Expand Up @@ -28,7 +28,7 @@
"homepage": "https://github.com/rohitg00/agentmemory#readme",
"bugs": "https://github.com/rohitg00/agentmemory/issues",
"dependencies": {
"@agentmemory/agentmemory": "0.9.30-chronode.5"
"@agentmemory/agentmemory": "0.9.30-chronode.6"
},
"publishConfig": {
"access": "public",
Expand Down
2 changes: 1 addition & 1 deletion plugin/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "agentmemory",
"version": "0.9.30-chronode.5",
"version": "0.9.30-chronode.6",
"description": "Persistent memory for AI coding agents -- captures tool usage, compresses via LLM, injects context into future sessions. 12 hooks, 60 MCP tools, 17 skills, real-time viewer.",
"author": {
"name": "Rohit Ghumare",
Expand Down
2 changes: 1 addition & 1 deletion plugin/.codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "agentmemory",
"version": "0.9.30-chronode.5",
"version": "0.9.30-chronode.6",
"description": "Persistent memory for AI coding agents -- captures tool usage, compresses via LLM, injects context into future sessions. 11 hooks, 60 MCP tools, 17 skills, real-time viewer.",
"author": {
"name": "Rohit Ghumare",
Expand Down
2 changes: 1 addition & 1 deletion plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "agentmemory",
"version": "0.9.30-chronode.5",
"version": "0.9.30-chronode.6",
"description": "Persistent memory for AI coding agents -- captures tool usage, compresses via LLM, injects context into future sessions. 12 hooks, 60 MCP tools, 17 skills, real-time viewer.",
"author": {
"name": "Rohit Ghumare",
Expand Down
2 changes: 1 addition & 1 deletion plugin/scripts/standalone.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1761,7 +1761,7 @@ function getAllTools() {
}
//#endregion
//#region src/version.ts
const VERSION = "0.9.30-chronode.5";
const VERSION = "0.9.30-chronode.6";
process.env["AGENTMEMORY_BUILD_ID"];
process.env["AGENTMEMORY_VIEWER_BUILD_ID"];
//#endregion
Expand Down
3 changes: 2 additions & 1 deletion plugin/skills/agentmemory-config/REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Generated by scanning `src/` for `AGENTMEMORY_*` usage. Do not edit the block below by hand; run `npm run skills:gen` after adding or removing a variable. Internal markers ending in two underscores are excluded.

<!-- AUTOGEN:env START - generated by scripts/skills/generate.ts, do not edit by hand -->
Configuration is read from the environment and from `~/.agentmemory/.env` (no `export` prefix). 68 recognized variables:
Configuration is read from the environment and from `~/.agentmemory/.env` (no `export` prefix). 69 recognized variables:

- `AGENTMEMORY_ADMIN_SECRET`
- `AGENTMEMORY_ADMIN_SECRET_FILE`
Expand Down Expand Up @@ -38,6 +38,7 @@ Configuration is read from the environment and from `~/.agentmemory/.env` (no `e
- `AGENTMEMORY_INJECT_CONTEXT`
- `AGENTMEMORY_LAUNCHD_LABELS`
- `AGENTMEMORY_LIVE_STREAM_MAX_BYTES`
- `AGENTMEMORY_LIVE_STREAM_PREV_TTL_DAYS`
- `AGENTMEMORY_LLM_TIMEOUT_MS`
- `AGENTMEMORY_LOCAL_PROCESSING`
- `AGENTMEMORY_MCP_BLOCK`
Expand Down
75 changes: 68 additions & 7 deletions src/state/live-stream-rotation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,18 +46,60 @@ export interface LiveStreamRotationOptions {
// subsequent publish would rename and rewrite ~cap bytes forever. One
// rotation per cooldown window bounds disk without the storm.
export const ROTATION_COOLDOWN_MS = 10 * 60_000;

let lastRotationAtMs = 0;

// How long a retired `.prev` generation is kept before the next rotation may
// discard it up front. Reclaiming an expired generation before the rename
// frees its bytes while the oversized current file is still in place; a fresh
// generation is left for the rename to replace atomically.
export const DEFAULT_LIVE_STREAM_PREV_TTL_DAYS = 30;
const DAY_MS = 24 * 60 * 60 * 1000;

export function resolveLiveStreamPrevTtlDays(
env: NodeJS.ProcessEnv = process.env,
): number {
const parsed = Number.parseInt(
env.AGENTMEMORY_LIVE_STREAM_PREV_TTL_DAYS ?? "",
10,
);
// Mirrors resolveLiveStreamMaxBytes: unset, unparsable, or negative values
// fall back to the default; zero is an explicit opt-out that keeps every
// .prev generation until the next rename replaces it.
if (!Number.isFinite(parsed) || parsed < 0) {
return DEFAULT_LIVE_STREAM_PREV_TTL_DAYS;
}
return parsed;
}

function isExpiredPreviousStream(
previousPath: string,
nowMs: number,
ttlDays: number,
): boolean {
if (ttlDays <= 0) return false;
try {
return statSync(previousPath).mtimeMs <= nowMs - ttlDays * DAY_MS;
} catch {
// An unstatable .prev (stat race) defers to the rename below, whose own
// catch reports any replacement failure.
return false;
}
}

// Rotates the viewer live stream when its persisted file has outgrown
// AGENTMEMORY_LIVE_STREAM_MAX_BYTES: the current file is renamed to
// `<path>.prev` (overwriting any previous generation) so the engine's next
// append starts a fresh file. The engine opens the store by path per append
// `<path>.prev` (replacing any previous generation) so the engine's next
// append starts a fresh file. An existing .prev older than
// AGENTMEMORY_LIVE_STREAM_PREV_TTL_DAYS days (default 30, 0 opts out) is
// unlinked before the rename so its bytes are reclaimed while the oversized
// current file still exists; the engine opens the store by path per append
// (no long-lived fd), so no engine restart or cooperation is needed.
//
// Best-effort by contract: every failure — missing file, stat error, rename
// race — degrades to "keep appending to the oversized file" and at most one
// warn line, because blocking or failing an observation publish to save disk
// would be the wrong trade.
// Best-effort by contract: every failure — missing file, stat error, stuck
// .prev removal, rename race — degrades to "keep appending to the oversized
// file" and at most one warn line per failed step, because blocking or
// failing an observation publish to save disk would be the wrong trade.
//
// Under vitest the default path would point at the operator's real store
// (tests share HOME and no engine owns the file there), so default-path
Expand Down Expand Up @@ -93,7 +135,26 @@ export function rotateLiveStreamIfOversized(

try {
const previousPath = `${filePath}.prev`;
if (existsSync(previousPath)) rmSync(previousPath);
if (
existsSync(previousPath) &&
isExpiredPreviousStream(
previousPath,
nowMs,
resolveLiveStreamPrevTtlDays(options.env),
)
) {
try {
rmSync(previousPath);
} catch (error) {
// Best-effort: an unremovable stale generation (permissions, occupied
// directory) must not block the rotation; the rename below replaces
// the target on its own where the platform allows it.
logger.warn("expired live stream .prev could not be removed", {
previousPath,
error: error instanceof Error ? error.message : String(error),
});
}
}
renameSync(filePath, previousPath);
lastRotationAtMs = nowMs;
return true;
Expand Down
2 changes: 1 addition & 1 deletion src/version.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export const VERSION = "0.9.30-chronode.5";
export const VERSION = "0.9.30-chronode.6";
export const EXPORT_FORMAT_VERSION = "0.9.28" as const;
export const API_CONTRACT_VERSION = 1;
export const BACKEND_BUILD_ID =
Expand Down
Loading
Loading