From 39b1e67af62ec5ad23b2003f2f043fde936aa65b Mon Sep 17 00:00:00 2001
From: Rohit Ghumare
Date: Sun, 2 Aug 2026 19:32:06 +0100
Subject: [PATCH 01/12] chore(release): v0.9.29 with project-scope parity
across surfaces
Version trio + plugin manifests + supportedVersions + ExportData union
bumped to 0.9.29; CHANGELOG entry covering everything since v0.9.28 with
upgrade notes for the four visible behavior changes.
Fixes the endpoint-count drift on main (130 registered routes vs docs
saying 129 after #1132 landed in parallel with #1136).
Project-scope parity: OpenCode plugin, Hermes plugin, Pi extension, and
JSONL replay now resolve project the same way the hooks do (env
override, git toplevel basename, cwd basename) instead of sending raw
filesystem paths, closing #903 and #1135 and pre-empting the same bug
in pi. The filesystem watcher accepts AGENTMEMORY_PROJECT_NAME with the
old AGENTMEMORY_PROJECT kept as a deprecated alias, replay handles
Windows-recorded paths, and OpenCode file enrichment matches the
agent's lowercase tool names (the capitalized set never matched).
Tests: opencode fallback expectations updated to basenames per the
canonicalization, git-toplevel resolution covered with a fixture repo,
new project-scope-parity suite for replay and fs-watcher.
---
AGENTS.md | 2 +-
CHANGELOG.md | 43 +++++++++++++
README.md | 2 +-
integrations/filesystem-watcher/watcher.mjs | 4 +-
integrations/hermes/__init__.py | 31 +++++++++-
integrations/pi/index.ts | 35 +++++++++--
package.json | 2 +-
plugin/.claude-plugin/plugin.json | 2 +-
plugin/.codex-plugin/plugin.json | 2 +-
plugin/opencode/agentmemory-capture.ts | 48 +++++++++++----
plugin/plugin.json | 2 +-
.../skills/agentmemory-rest-api/REFERENCE.md | 2 +-
src/functions/export-import.ts | 2 +-
src/index.ts | 2 +-
src/replay/jsonl-parser.ts | 4 +-
src/types.ts | 2 +-
src/version.ts | 2 +-
test/export-import.test.ts | 3 +-
test/opencode-auto-context.test.ts | 55 ++++++++++++++---
test/project-scope-parity.test.ts | 60 +++++++++++++++++++
20 files changed, 264 insertions(+), 41 deletions(-)
create mode 100644 test/project-scope-parity.test.ts
diff --git a/AGENTS.md b/AGENTS.md
index 6f64946fc..9a5a55467 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -117,7 +117,7 @@ Hook scripts in `src/hooks/` are standalone Node.js scripts (no iii-sdk import).
## Current Stats (v0.9.28)
- 54 MCP tools (8 visible by default, `AGENTMEMORY_TOOLS=all` for all)
-- 129 REST endpoints
+- 130 REST endpoints
- 6 MCP resources, 3 MCP prompts
- 12 hooks, 15 skills
- 260+ iii functions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 24a1722e8..8914491dd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,49 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
## [Unreleased]
+## [0.9.29] — 2026-08-02
+
+Patch release: the `.env` file now actually applies everywhere, imports become searchable, consolidation runs on session stop, twelve MCP-only agents get activated on connect, and every capture surface finally agrees on what "project" means. No breaking changes; read the upgrade notes below for four behavior changes you will notice.
+
+### Upgrade notes
+
+- `~/.agentmemory/.env` values that were silently ignored by most modules now take effect on boot. If that file has stale entries from past experiments, review it before upgrading.
+- `agentmemory connect ` now writes a short memory-usage guideline into the agent's native rules file (Cursor, Cline, Continue, Zed, Warp, Kiro, Gemini CLI, Qwen, OpenCode, Droid, Copilot CLI, Antigravity) so MCP-only agents actually call the memory tools. Pass `--no-guidelines` to opt out.
+- Installs with an LLM key now run consolidation and crystallization on session stop (previously they never fired), debounced to once per 5 minutes (`AGENTMEMORY_CONSOLIDATION_COOLDOWN_MS`).
+- Local embeddings re-download once after the `@huggingface/transformers` migration (different model cache directory). Model IDs are unchanged.
+
+### Added
+
+- `--data-dir` flag and `AGENTMEMORY_DATA_DIR` so iii-engine state lives outside repositories, with gated legacy `./data` adoption and Docker-volume preservation (#314)
+- Native hooks adapter for Droid via `~/.factory/hooks.json`, reusing the bundled hook scripts (#1130)
+- `mem::graph::import-graphify` and `POST /agentmemory/graph/import-graphify`: merge graphify's `graph.json` into the knowledge graph with confidence tags carried over as edge weights (#1136)
+- Connector guideline activation for twelve hook-less agents, with every rules-file path verified against the agent's official documentation (#1136)
+- Honest `memory_forget` reporting plus a real lesson delete path (`mem::lesson-delete`, `DELETE`-style REST route, MCP tool) (#1132)
+- `AGENTMEMORY_PROJECT_NAME` override in the OpenCode plugin (#1125)
+- Provider fetches retry 429/503 honoring `Retry-After` under a total-elapsed budget capped below the iii invocation timeout (#1136)
+
+### Fixed
+
+- Boot hydrates `~/.agentmemory/.env` into `process.env`, closing the class of "env var in .env is ignored" bugs (#1136)
+- Imported and replayed observations are indexed into BM25 and the vector index, so imports are searchable (#1072, via #1136)
+- Snapshot timer actually runs, non-positive intervals clamp to the default, and snapshot creation is serialized across timer, REST, and MCP (#1006, via #1136)
+- CJK-aware dedup with NFC normalization and an exact-match fallback for short memories (#1021, via #1136)
+- OpenRouter embeddings no longer hardcode 1536 dimensions (#1002, via #1136)
+- Viewer decodes multibyte request bodies correctly (#930, via #1136)
+- Session-stop consolidation is debounced and no longer double-fires from the client hook; eviction recovery is bounded to one consolidation pass (#1087, #1131 class, via #1136)
+- `/agentmemory/sessions` no longer deadlocks on large session counts (#1100, via #1136)
+- Filesystem watcher validates roots before `fs.watch`, fixing Node 24/26 on Linux (#1136)
+- Claude bridge writes `MEMORY.md` under the `memory/` subdirectory Claude Code actually reads (#1134)
+- Hook project-resolution tests no longer depend on the checkout directory name (#1137, #1138)
+- Project-scope parity: the OpenCode plugin, Hermes plugin, Pi extension, and JSONL replay now resolve `project` the same way the hooks do (env override, git toplevel basename, cwd basename) instead of sending raw filesystem paths, so the same repository shares one memory bucket across agents (#903, #1135); the filesystem watcher accepts `AGENTMEMORY_PROJECT_NAME` with the old `AGENTMEMORY_PROJECT` kept as a deprecated alias; replay handles Windows-recorded paths
+- OpenCode file enrichment matches the agent's lowercase tool names, which the previous capitalized set never did
+- Viewer surfaces health status from non-2xx health responses (#1046)
+- Documented REST endpoint count matches the registered routes again (130)
+
+### Changed
+
+- Local embeddings migrate from `@xenova/transformers` to `@huggingface/transformers` v4 with Node 22+ support; CI now tests Node 20, 22, 24, and 26 (#479, #1096)
+
## [0.9.28] — 2026-07-19
Patch release: hardens the hook runner against malformed payloads and closes a cross-agent context leak. No breaking changes; drop-in upgrade.
diff --git a/README.md b/README.md
index 332e2010c..655347994 100644
--- a/README.md
+++ b/README.md
@@ -1498,7 +1498,7 @@ Create `~/.agentmemory/.env`:
-129 endpoints on port `3111`. The REST API binds to `127.0.0.1` by default. Protected endpoints require `Authorization: Bearer ` when `AGENTMEMORY_SECRET` is set, and mesh sync endpoints require `AGENTMEMORY_SECRET` on both peers.
+130 endpoints on port `3111`. The REST API binds to `127.0.0.1` by default. Protected endpoints require `Authorization: Bearer ` when `AGENTMEMORY_SECRET` is set, and mesh sync endpoints require `AGENTMEMORY_SECRET` on both peers.
Key endpoints
diff --git a/integrations/filesystem-watcher/watcher.mjs b/integrations/filesystem-watcher/watcher.mjs
index a73d178f3..5e203305e 100644
--- a/integrations/filesystem-watcher/watcher.mjs
+++ b/integrations/filesystem-watcher/watcher.mjs
@@ -319,7 +319,9 @@ export function configFromEnv(env = process.env) {
roots,
baseUrl: env.AGENTMEMORY_URL,
secret: env.AGENTMEMORY_SECRET,
- project: env.AGENTMEMORY_PROJECT || null,
+ // AGENTMEMORY_PROJECT_NAME is the canonical override (matches the hooks);
+ // AGENTMEMORY_PROJECT stays as a deprecated alias for existing setups.
+ project: env.AGENTMEMORY_PROJECT_NAME || env.AGENTMEMORY_PROJECT || null,
sessionId: env.AGENTMEMORY_SESSION_ID || null,
ignorePatterns: extraIgnore,
allowBinary: env.AGENTMEMORY_FS_WATCH_ALLOW_BINARY === "1",
diff --git a/integrations/hermes/__init__.py b/integrations/hermes/__init__.py
index 2933632d0..79ab21889 100644
--- a/integrations/hermes/__init__.py
+++ b/integrations/hermes/__init__.py
@@ -13,6 +13,30 @@
import os
import sys
import threading
+import subprocess
+from pathlib import PurePath
+
+
+def _resolve_project(cwd: str) -> str:
+ """Canonical project scope, matching the hooks' resolveProject order:
+ AGENTMEMORY_PROJECT_NAME env override, git toplevel basename, cwd basename.
+ Keeps Hermes sessions in the same project bucket as every other agent."""
+ explicit = os.environ.get("AGENTMEMORY_PROJECT_NAME", "").strip()
+ if explicit:
+ return explicit
+ try:
+ top = subprocess.run(
+ ["git", "rev-parse", "--show-toplevel"],
+ cwd=cwd,
+ capture_output=True,
+ text=True,
+ timeout=5,
+ ).stdout.strip()
+ if top:
+ return PurePath(top).name
+ except Exception:
+ pass
+ return PurePath(cwd).name or cwd
import time
from pathlib import Path
from typing import Any, Callable
@@ -188,14 +212,15 @@ def is_available(self) -> bool:
def initialize(self, session_id: str, **kwargs: Any) -> None:
self._base = os.environ.get("AGENTMEMORY_URL", DEFAULT_BASE_URL)
self._session_id = session_id
- self._project = kwargs.get("cwd", os.getcwd())
+ self._cwd = kwargs.get("cwd", os.getcwd())
+ self._project = _resolve_project(self._cwd)
if os.environ.get("AGENTMEMORY_REQUIRE_HTTPS") == "1":
_check_plaintext_bearer_guard(self._base, os.environ.get("AGENTMEMORY_SECRET", ""))
_api(self._base, "session/start", {
"sessionId": session_id,
"project": self._project,
- "cwd": self._project,
+ "cwd": self._cwd,
})
def get_config_schema(self) -> list[dict]:
@@ -348,7 +373,7 @@ def sync_turn(self, user: str, assistant: str, **kwargs: Any) -> None:
"hookType": "post_tool_use",
"sessionId": kwargs.get("session_id", self._session_id),
"project": self._project,
- "cwd": self._project,
+ "cwd": self._cwd,
"timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
"data": {
"tool_name": "conversation",
diff --git a/integrations/pi/index.ts b/integrations/pi/index.ts
index 9c6cfc702..e6ad648de 100644
--- a/integrations/pi/index.ts
+++ b/integrations/pi/index.ts
@@ -2,6 +2,7 @@ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
import { Type } from "typebox";
import path from "node:path";
import crypto from "node:crypto";
+import { execFileSync } from "node:child_process";
import { createPlaintextBearerAuthGuard } from "./security.js";
type TextBlock = { type?: string; text?: string };
@@ -120,7 +121,31 @@ export default function agentmemoryExtension(pi: ExtensionAPI) {
);
}
let sessionId = `ephemeral-${crypto.randomUUID().slice(0, 8)}`;
- let currentProject = process.cwd();
+ // Canonical project scope, matching the hooks' resolveProject order (env
+ // override, git toplevel basename, cwd basename) so Pi sessions share a
+ // project bucket with every other agent instead of scoping on a raw path.
+ const projectCache = new Map();
+ function resolveProjectName(dir: string): string {
+ const explicit = process.env["AGENTMEMORY_PROJECT_NAME"]?.trim();
+ if (explicit) return explicit;
+ const cached = projectCache.get(dir);
+ if (cached) return cached;
+ let name = path.basename(dir) || dir;
+ try {
+ const top = execFileSync("git", ["rev-parse", "--show-toplevel"], {
+ cwd: dir,
+ stdio: ["ignore", "pipe", "ignore"],
+ encoding: "utf8",
+ }).trim();
+ if (top) name = path.basename(top);
+ } catch {
+ // not a git repo
+ }
+ projectCache.set(dir, name);
+ return name;
+ }
+ let currentCwd = process.cwd();
+ let currentProject = resolveProjectName(currentCwd);
let lastPrompt = "";
let lastHealthOk = false;
@@ -227,12 +252,14 @@ export default function agentmemoryExtension(pi: ExtensionAPI) {
pi.on("session_start", async (_event, ctx) => {
const sessionFile = ctx.sessionManager.getSessionFile();
sessionId = sessionFile ? path.basename(sessionFile).replace(/\.[^.]+$/, "") : `ephemeral-${crypto.randomUUID().slice(0, 8)}`;
- currentProject = process.cwd();
+ currentCwd = process.cwd();
+ currentProject = resolveProjectName(currentCwd);
await refreshStatus(ctx);
});
pi.on("before_agent_start", async (event, ctx) => {
- currentProject = event.systemPromptOptions.cwd || process.cwd();
+ currentCwd = event.systemPromptOptions.cwd || process.cwd();
+ currentProject = resolveProjectName(currentCwd);
lastPrompt = event.prompt?.trim() || "";
if (!lastPrompt) return;
@@ -262,7 +289,7 @@ export default function agentmemoryExtension(pi: ExtensionAPI) {
hookType: "post_tool_use",
sessionId,
project: currentProject,
- cwd: currentProject,
+ cwd: currentCwd,
timestamp: new Date().toISOString(),
data: {
tool_name: "conversation",
diff --git a/package.json b/package.json
index 77185ad5f..79b716c92 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@agentmemory/agentmemory",
- "version": "0.9.28",
+ "version": "0.9.29",
"description": "Persistent memory for AI coding agents, powered by iii-engine's three primitives",
"type": "module",
"main": "dist/index.mjs",
diff --git a/plugin/.claude-plugin/plugin.json b/plugin/.claude-plugin/plugin.json
index 27bdc81eb..52fc79280 100644
--- a/plugin/.claude-plugin/plugin.json
+++ b/plugin/.claude-plugin/plugin.json
@@ -1,6 +1,6 @@
{
"name": "agentmemory",
- "version": "0.9.28",
+ "version": "0.9.29",
"description": "Persistent memory for AI coding agents -- captures tool usage, compresses via LLM, injects context into future sessions. 12 hooks, 54 MCP tools, 8 skills, real-time viewer.",
"author": {
"name": "Rohit Ghumare",
diff --git a/plugin/.codex-plugin/plugin.json b/plugin/.codex-plugin/plugin.json
index ad262621d..cbcd5731a 100644
--- a/plugin/.codex-plugin/plugin.json
+++ b/plugin/.codex-plugin/plugin.json
@@ -1,6 +1,6 @@
{
"name": "agentmemory",
- "version": "0.9.28",
+ "version": "0.9.29",
"description": "Persistent memory for AI coding agents -- captures tool usage, compresses via LLM, injects context into future sessions. 6 hooks, 54 MCP tools, 8 skills, real-time viewer.",
"author": {
"name": "Rohit Ghumare",
diff --git a/plugin/opencode/agentmemory-capture.ts b/plugin/opencode/agentmemory-capture.ts
index 46419ef8c..1a1d04268 100644
--- a/plugin/opencode/agentmemory-capture.ts
+++ b/plugin/opencode/agentmemory-capture.ts
@@ -1,7 +1,12 @@
import type { Plugin } from "@opencode-ai/plugin";
+import { execFileSync } from "node:child_process";
+import { basename } from "node:path";
const API = process.env.AGENTMEMORY_URL || "http://localhost:3111";
-const FILE_TOOLS = new Set(["Read", "Write", "Edit", "Glob", "Grep"]);
+// OpenCode reports tool names in lowercase ("read", "edit", ...); matching is
+// case-insensitive at the call site so a future casing change cannot silently
+// kill file enrichment again.
+const FILE_TOOLS = new Set(["read", "write", "edit", "glob", "grep"]);
const FILE_KEYS = ["filePath", "file_path", "path", "file", "pattern"];
const MAX_STASHED_FILES = 20;
@@ -50,8 +55,8 @@ async function observe(
await post("/observe", {
hookType,
sessionId,
- project: projectPath,
- cwd: projectPath,
+ project: projectName,
+ cwd: projectCwd,
timestamp: new Date().toISOString(),
data,
});
@@ -59,7 +64,28 @@ async function observe(
let activeSessionId: string | null = null;
let pendingConfig: Record | null = null;
-let projectPath: string | null = null;
+// projectName is the canonical scope (same resolution order as the hooks'
+// resolveProject: env override, git toplevel basename, cwd basename) so
+// OpenCode sessions land in the same project bucket as every other agent on
+// the repo. projectCwd keeps the full path for the cwd field.
+let projectName: string | null = null;
+let projectCwd: string | null = null;
+
+function resolveProjectName(dir: string): string {
+ const explicit = process.env.AGENTMEMORY_PROJECT_NAME?.trim();
+ if (explicit) return explicit;
+ try {
+ const top = execFileSync("git", ["rev-parse", "--show-toplevel"], {
+ cwd: dir,
+ stdio: ["ignore", "pipe", "ignore"],
+ encoding: "utf8",
+ }).trim();
+ if (top) return basename(top);
+ } catch {
+ // not a git repo, fall through
+ }
+ return basename(dir) || dir;
+}
const stashedFiles = new Map>();
const seenSubtaskIds = new Map>();
const seenToolCallIds = new Map>();
@@ -168,8 +194,8 @@ function extractErrorMessage(err: unknown): string {
}
export const AgentmemoryCapturePlugin: Plugin = async (ctx) => {
- const explicitProject = process.env.AGENTMEMORY_PROJECT_NAME?.trim();
- projectPath = explicitProject || ctx.worktree || ctx.project?.id || process.cwd();
+ projectCwd = ctx.worktree || ctx.project?.id || process.cwd();
+ projectName = resolveProjectName(projectCwd);
return {
event: async ({ event }) => {
@@ -194,8 +220,8 @@ export const AgentmemoryCapturePlugin: Plugin = async (ctx) => {
title: info?.title ?? null,
parentID: info?.parentID ?? null,
version: info?.version ?? null,
- project: projectPath,
- cwd: projectPath,
+ project: projectName,
+ cwd: projectCwd,
});
// cache the context returned at session/start so the
// chat.system.transform hook injects it without a second fetch.
@@ -582,7 +608,7 @@ export const AgentmemoryCapturePlugin: Plugin = async (ctx) => {
// ── tool.execute.before ──
"tool.execute.before": async (input, output) => {
- if (!FILE_TOOLS.has(input.tool)) return;
+ if (!FILE_TOOLS.has(String(input.tool ?? "").toLowerCase())) return;
const sid = input.sessionID || activeSessionId;
if (!sid) return;
const args = output.args as Record | undefined;
@@ -613,7 +639,7 @@ export const AgentmemoryCapturePlugin: Plugin = async (ctx) => {
if (typeof ctx !== "string" || ctx.length === 0) {
const result = await postJson("/context", {
sessionId: sid,
- project: projectPath,
+ project: projectName,
});
ctx = (result as any)?.context;
} else {
@@ -651,7 +677,7 @@ export const AgentmemoryCapturePlugin: Plugin = async (ctx) => {
const result = await postJson("/context", {
sessionId: sid,
- project: projectPath,
+ project: projectName,
});
const ctx = (result as any)?.context;
if (typeof ctx === "string" && ctx.length > 0) {
diff --git a/plugin/plugin.json b/plugin/plugin.json
index 90d248c58..ad8025aec 100644
--- a/plugin/plugin.json
+++ b/plugin/plugin.json
@@ -1,6 +1,6 @@
{
"name": "agentmemory",
- "version": "0.9.28",
+ "version": "0.9.29",
"description": "Persistent memory for AI coding agents -- captures tool usage, compresses via LLM, injects context into future sessions. 12 hooks, 54 MCP tools, 15 skills, real-time viewer.",
"author": {
"name": "Rohit Ghumare",
diff --git a/plugin/skills/agentmemory-rest-api/REFERENCE.md b/plugin/skills/agentmemory-rest-api/REFERENCE.md
index d12ff4610..b92e35a9e 100644
--- a/plugin/skills/agentmemory-rest-api/REFERENCE.md
+++ b/plugin/skills/agentmemory-rest-api/REFERENCE.md
@@ -5,7 +5,7 @@ Generated from `src/triggers/api.ts`. Do not edit the block below by hand; run `
The REST API is the primary surface. All paths are under `http://localhost:3111` (override with `--port`). When `AGENTMEMORY_SECRET` is set, send `Authorization: Bearer $AGENTMEMORY_SECRET`; localhost is otherwise open.
-118 registered endpoints:
+119 registered endpoints:
| Method | Path |
| --- | --- |
diff --git a/src/functions/export-import.ts b/src/functions/export-import.ts
index 2e30e070f..740037aab 100644
--- a/src/functions/export-import.ts
+++ b/src/functions/export-import.ts
@@ -200,7 +200,7 @@ export function registerExportImportFunction(sdk: ISdk, kv: StateKV): void {
const strategy = data.strategy || "merge";
const importData = data.exportData;
- const supportedVersions = new Set(["0.3.0", "0.4.0", "0.5.0", "0.6.0", "0.6.1", "0.7.0", "0.7.2", "0.7.3", "0.7.4", "0.7.5", "0.7.6", "0.7.7", "0.7.9", "0.8.0", "0.8.1", "0.8.2", "0.8.3", "0.8.4", "0.8.5", "0.8.6", "0.8.7", "0.8.8", "0.8.9", "0.8.10", "0.8.11", "0.8.12", "0.8.13", "0.9.0", "0.9.1", "0.9.2", "0.9.3", "0.9.4", "0.9.5", "0.9.6", "0.9.7", "0.9.8", "0.9.9", "0.9.10", "0.9.11", "0.9.12", "0.9.13", "0.9.14", "0.9.15", "0.9.16", "0.9.17", "0.9.18", "0.9.19", "0.9.20", "0.9.21", "0.9.22", "0.9.23", "0.9.24", "0.9.25", "0.9.26", "0.9.27", "0.9.28"]);
+ const supportedVersions = new Set(["0.3.0", "0.4.0", "0.5.0", "0.6.0", "0.6.1", "0.7.0", "0.7.2", "0.7.3", "0.7.4", "0.7.5", "0.7.6", "0.7.7", "0.7.9", "0.8.0", "0.8.1", "0.8.2", "0.8.3", "0.8.4", "0.8.5", "0.8.6", "0.8.7", "0.8.8", "0.8.9", "0.8.10", "0.8.11", "0.8.12", "0.8.13", "0.9.0", "0.9.1", "0.9.2", "0.9.3", "0.9.4", "0.9.5", "0.9.6", "0.9.7", "0.9.8", "0.9.9", "0.9.10", "0.9.11", "0.9.12", "0.9.13", "0.9.14", "0.9.15", "0.9.16", "0.9.17", "0.9.18", "0.9.19", "0.9.20", "0.9.21", "0.9.22", "0.9.23", "0.9.24", "0.9.25", "0.9.26", "0.9.27", "0.9.28", "0.9.29"]);
if (!supportedVersions.has(importData.version)) {
return {
success: false,
diff --git a/src/index.ts b/src/index.ts
index 5f66d76c9..198a6dc3d 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -540,7 +540,7 @@ async function main() {
`Ready. ${embeddingProvider ? "Triple-stream (BM25+Vector+Graph)" : "BM25+Graph"} search active.`,
);
bootLog(
- `REST API: 129 endpoints at http://localhost:${config.restPort}/agentmemory/*`,
+ `REST API: 130 endpoints at http://localhost:${config.restPort}/agentmemory/*`,
);
bootLog(
`MCP surface (opt-in via \`npx @agentmemory/mcp\`): ${getAllTools().length} tools · 6 resources · 3 prompts`,
diff --git a/src/replay/jsonl-parser.ts b/src/replay/jsonl-parser.ts
index 5060c3451..fc4b0811b 100644
--- a/src/replay/jsonl-parser.ts
+++ b/src/replay/jsonl-parser.ts
@@ -26,7 +26,9 @@ export interface ParsedTranscript {
function deriveProject(cwd: string): string {
if (!cwd) return "unknown";
- const parts = cwd.split("/").filter(Boolean);
+ // Split on both separators so a Windows-recorded cwd yields its basename
+ // instead of the whole raw path becoming the project scope.
+ const parts = cwd.split(/[\\/]+/).filter(Boolean);
return parts[parts.length - 1] || "unknown";
}
diff --git a/src/types.ts b/src/types.ts
index 7cda80ffb..2f3f0285f 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -307,7 +307,7 @@ export interface ExportPagination {
}
export interface ExportData {
- version: "0.3.0" | "0.4.0" | "0.5.0" | "0.6.0" | "0.6.1" | "0.7.0" | "0.7.2" | "0.7.3" | "0.7.4" | "0.7.5" | "0.7.6" | "0.7.7" | "0.7.9" | "0.8.0" | "0.8.1" | "0.8.2" | "0.8.3" | "0.8.4" | "0.8.5" | "0.8.6" | "0.8.7" | "0.8.8" | "0.8.9" | "0.8.10" | "0.8.11" | "0.8.12" | "0.8.13" | "0.9.0" | "0.9.1" | "0.9.2" | "0.9.3" | "0.9.4" | "0.9.5" | "0.9.6" | "0.9.7" | "0.9.8" | "0.9.9" | "0.9.10" | "0.9.11" | "0.9.12" | "0.9.13" | "0.9.14" | "0.9.15" | "0.9.16" | "0.9.17" | "0.9.18" | "0.9.19" | "0.9.20" | "0.9.21" | "0.9.22" | "0.9.23" | "0.9.24" | "0.9.25" | "0.9.26" | "0.9.27" | "0.9.28";
+ version: "0.3.0" | "0.4.0" | "0.5.0" | "0.6.0" | "0.6.1" | "0.7.0" | "0.7.2" | "0.7.3" | "0.7.4" | "0.7.5" | "0.7.6" | "0.7.7" | "0.7.9" | "0.8.0" | "0.8.1" | "0.8.2" | "0.8.3" | "0.8.4" | "0.8.5" | "0.8.6" | "0.8.7" | "0.8.8" | "0.8.9" | "0.8.10" | "0.8.11" | "0.8.12" | "0.8.13" | "0.9.0" | "0.9.1" | "0.9.2" | "0.9.3" | "0.9.4" | "0.9.5" | "0.9.6" | "0.9.7" | "0.9.8" | "0.9.9" | "0.9.10" | "0.9.11" | "0.9.12" | "0.9.13" | "0.9.14" | "0.9.15" | "0.9.16" | "0.9.17" | "0.9.18" | "0.9.19" | "0.9.20" | "0.9.21" | "0.9.22" | "0.9.23" | "0.9.24" | "0.9.25" | "0.9.26" | "0.9.27" | "0.9.28" | "0.9.29";
exportedAt: string;
sessions: Session[];
observations: Record;
diff --git a/src/version.ts b/src/version.ts
index 6d09f4f4a..84d83bdcb 100644
--- a/src/version.ts
+++ b/src/version.ts
@@ -1 +1 @@
-export const VERSION = "0.9.28";
+export const VERSION = "0.9.29";
diff --git a/test/export-import.test.ts b/test/export-import.test.ts
index a345ca3d5..d33aacb1e 100644
--- a/test/export-import.test.ts
+++ b/test/export-import.test.ts
@@ -5,6 +5,7 @@ vi.mock("../src/logger.js", () => ({
}));
import { registerExportImportFunction } from "../src/functions/export-import.js";
+import { VERSION } from "../src/version.js";
import { getSearchIndex } from "../src/functions/search.js";
import type {
Session,
@@ -124,7 +125,7 @@ describe("Export/Import Functions", () => {
it("export produces valid ExportData structure", async () => {
const result = (await sdk.trigger("mem::export", {})) as ExportData;
- expect(result.version).toBe("0.9.28");
+ expect(result.version).toBe(VERSION);
expect(result.exportedAt).toBeDefined();
expect(result.sessions.length).toBe(1);
expect(result.sessions[0].id).toBe("ses_1");
diff --git a/test/opencode-auto-context.test.ts b/test/opencode-auto-context.test.ts
index 2691e0965..dd59b0411 100644
--- a/test/opencode-auto-context.test.ts
+++ b/test/opencode-auto-context.test.ts
@@ -53,7 +53,9 @@ describe("OpenCode plugin project name resolution", () => {
else process.env.AGENTMEMORY_PROJECT_NAME = savedProjectName;
});
- async function projectFor(ctx: Record): Promise {
+ async function startPayloadFor(
+ ctx: Record,
+ ): Promise<{ project: unknown; cwd: unknown }> {
const { AgentmemoryCapturePlugin } = await import(
"../plugin/opencode/agentmemory-capture.ts"
);
@@ -67,7 +69,12 @@ describe("OpenCode plugin project name resolution", () => {
(c: unknown[]) => typeof c[0] === "string" && (c[0] as string).includes("/session/start"),
);
if (!startCall) throw new Error("no /session/start call captured");
- return JSON.parse((startCall[1] as { body: string }).body).project;
+ const body = JSON.parse((startCall[1] as { body: string }).body);
+ return { project: body.project, cwd: body.cwd };
+ }
+
+ async function projectFor(ctx: Record): Promise {
+ return (await startPayloadFor(ctx)).project;
}
it("uses trimmed AGENTMEMORY_PROJECT_NAME when set", async () => {
@@ -75,20 +82,50 @@ describe("OpenCode plugin project name resolution", () => {
expect(await projectFor({ worktree: "/should/be/ignored" })).toBe("my-proj");
});
- it("treats whitespace-only env value as unset and falls back", async () => {
+ it("treats whitespace-only env value as unset and falls back to the basename", async () => {
process.env.AGENTMEMORY_PROJECT_NAME = " ";
- expect(await projectFor({ worktree: "/repo/alpha" })).toBe("/repo/alpha");
+ expect(await projectFor({ worktree: "/repo/alpha" })).toBe("alpha");
});
- it("falls back to ctx.worktree when env is unset", async () => {
- expect(await projectFor({ worktree: "/repo/alpha" })).toBe("/repo/alpha");
+ // Canonicalization: project is the git-toplevel/cwd BASENAME (matching the
+ // hooks' resolveProject), while cwd keeps the full path. A nonexistent dir
+ // cannot be a git repo, so these exercise the basename fallback.
+ it("sends the basename as project and the full path as cwd", async () => {
+ const payload = await startPayloadFor({ worktree: "/repo/alpha" });
+ expect(payload.project).toBe("alpha");
+ expect(payload.cwd).toBe("/repo/alpha");
});
it("falls back to ctx.project.id when worktree is absent", async () => {
- expect(await projectFor({ project: { id: "/repo/beta" } })).toBe("/repo/beta");
+ expect(await projectFor({ project: { id: "/repo/beta" } })).toBe("beta");
});
- it("falls back to process.cwd() when no ctx field is present", async () => {
- expect(await projectFor({})).toBe(process.cwd());
+ it("resolves the git toplevel basename inside a real repository", async () => {
+ const { mkdtempSync, mkdirSync, rmSync } = await import("node:fs");
+ const { tmpdir } = await import("node:os");
+ const { join } = await import("node:path");
+ const { execFileSync } = await import("node:child_process");
+ const root = mkdtempSync(join(tmpdir(), "amem-oc-"));
+ const repo = join(root, "oc-fixture-repo");
+ const nested = join(repo, "src", "deep");
+ mkdirSync(nested, { recursive: true });
+ execFileSync("git", ["init", "--quiet"], { cwd: repo, stdio: "ignore" });
+ try {
+ // Subdirectory of the repo still resolves to the repo basename.
+ expect(await projectFor({ worktree: nested })).toBe("oc-fixture-repo");
+ } finally {
+ rmSync(root, { recursive: true, force: true });
+ }
+ });
+});
+
+describe("OpenCode plugin file-tool matching", () => {
+ const plugin = readFileSync("plugin/opencode/agentmemory-capture.ts", "utf-8");
+
+ it("matches OpenCode's lowercase tool names case-insensitively", () => {
+ // OpenCode reports "read"/"edit"/... in lowercase; the old capitalized
+ // set never matched, silently disabling file enrichment.
+ expect(plugin).toContain('FILE_TOOLS = new Set(["read", "write", "edit", "glob", "grep"])');
+ expect(plugin).toContain('FILE_TOOLS.has(String(input.tool ?? "").toLowerCase())');
});
});
diff --git a/test/project-scope-parity.test.ts b/test/project-scope-parity.test.ts
new file mode 100644
index 000000000..28ef8b89f
--- /dev/null
+++ b/test/project-scope-parity.test.ts
@@ -0,0 +1,60 @@
+import { describe, it, expect } from "vitest";
+import { parseJsonlText } from "../src/replay/jsonl-parser.js";
+// @ts-expect-error plain .mjs module without type declarations
+import { configFromEnv } from "../integrations/filesystem-watcher/watcher.mjs";
+
+// Project-scope parity: every capture surface must resolve `project` the same
+// way the hooks do (env override, git toplevel basename, cwd basename), or the
+// same repo fragments into per-agent memory buckets that never cross-recall.
+
+function transcriptLine(cwd: string): string {
+ return JSON.stringify({
+ type: "user",
+ uuid: "u1",
+ sessionId: "sess-parity",
+ timestamp: "2026-08-01T10:00:00.000Z",
+ cwd,
+ message: { role: "user", content: [{ type: "text", text: "hello" }] },
+ });
+}
+
+describe("replay deriveProject (via parseJsonlText)", () => {
+ it("uses the basename of a posix cwd", () => {
+ const parsed = parseJsonlText(transcriptLine("/home/dev/myrepo"));
+ expect(parsed.project).toBe("myrepo");
+ });
+
+ it("uses the basename of a Windows cwd instead of the whole raw path", () => {
+ const parsed = parseJsonlText(transcriptLine("C:\\Users\\dev\\myrepo"));
+ expect(parsed.project).toBe("myrepo");
+ });
+
+ it("handles mixed separators", () => {
+ const parsed = parseJsonlText(transcriptLine("C:\\Users\\dev/myrepo"));
+ expect(parsed.project).toBe("myrepo");
+ });
+});
+
+describe("fs-watcher configFromEnv project override", () => {
+ it("prefers the canonical AGENTMEMORY_PROJECT_NAME", () => {
+ const cfg = configFromEnv({
+ AGENTMEMORY_FS_WATCH: "/tmp",
+ AGENTMEMORY_PROJECT_NAME: "canonical-name",
+ AGENTMEMORY_PROJECT: "legacy-name",
+ });
+ expect(cfg.project).toBe("canonical-name");
+ });
+
+ it("falls back to the deprecated AGENTMEMORY_PROJECT alias", () => {
+ const cfg = configFromEnv({
+ AGENTMEMORY_FS_WATCH: "/tmp",
+ AGENTMEMORY_PROJECT: "legacy-name",
+ });
+ expect(cfg.project).toBe("legacy-name");
+ });
+
+ it("is null when neither is set (watcher derives from the root basename)", () => {
+ const cfg = configFromEnv({ AGENTMEMORY_FS_WATCH: "/tmp" });
+ expect(cfg.project).toBeNull();
+ });
+});
From a8a6d1a87e8cc2b3975b8cebc2d194d8fc48acc6 Mon Sep 17 00:00:00 2001
From: Rohit Ghumare
Date: Sun, 2 Aug 2026 20:51:25 +0100
Subject: [PATCH 02/12] fix(release): review findings, git-toplevel parity, doc
counts
- skills generator dedupes routes on method plus path, so the REST
reference lists all 130 registered routes instead of hiding the second
method on ten dual-method paths (header said 119)
- fs-watcher trims AGENTMEMORY_PROJECT_NAME and the deprecated alias,
treating whitespace as unset, and derives the git toplevel basename
when watching a subdirectory
- replay resolves the git toplevel basename when the recorded cwd still
exists locally (memoized per cwd), keeping the basename fallback for
historical or cross-platform paths; no env override here since a bulk
import spans many projects
- parity tests for replay git-root resolution, watcher git-root and
trim behavior
- stat-tests badge updated from 1428+ to 1550+ passing
---
assets/tags/light/stat-tests.svg | 4 +-
assets/tags/stat-tests.svg | 4 +-
integrations/filesystem-watcher/watcher.mjs | 26 +++++-
.../skills/agentmemory-rest-api/REFERENCE.md | 13 ++-
scripts/skills/generate.ts | 10 ++-
src/replay/jsonl-parser.ts | 37 ++++++++-
test/project-scope-parity.test.ts | 79 ++++++++++++++++++-
7 files changed, 159 insertions(+), 14 deletions(-)
diff --git a/assets/tags/light/stat-tests.svg b/assets/tags/light/stat-tests.svg
index a309d2c74..f161f25f4 100644
--- a/assets/tags/light/stat-tests.svg
+++ b/assets/tags/light/stat-tests.svg
@@ -1,5 +1,5 @@
-
@@ -1209,7 +1209,7 @@ Full registry: [workers.iii.dev](https://workers.iii.dev). Every worker there co
| Prometheus / Grafana | iii OTEL + health monitor |
| Custom plugin systems | `iii worker add ` |
-**175 source files · ~39,200 LOC · 1,428+ tests · 261 functions · 52 KV scopes** — all on three primitives. No `agentmemory plugin install`. The plugin system is iii itself.
+**175 source files · ~39,200 LOC · 1,596+ tests · 261 functions · 52 KV scopes** — all on three primitives. No `agentmemory plugin install`. The plugin system is iii itself.
---
@@ -1533,7 +1533,7 @@ Full endpoint list: [`src/triggers/api.ts`](src/triggers/api.ts)
```bash
npm run dev # Hot reload
npm run build # Production build
-npm test # 1,428+ tests
+npm test # 1,596+ tests
npm run test:integration # API tests (requires running services)
```
diff --git a/deploy/coolify/Dockerfile b/deploy/coolify/Dockerfile
index e95bd70ec..c0a6bb6c9 100644
--- a/deploy/coolify/Dockerfile
+++ b/deploy/coolify/Dockerfile
@@ -4,7 +4,7 @@ FROM iiidev/iii:${III_VERSION} AS iii-image
FROM node:22-slim
-ARG AGENTMEMORY_VERSION=0.9.28
+ARG AGENTMEMORY_VERSION=0.9.29
ARG III_VERSION=0.11.2
ARG III_SDK_VERSION=0.11.2
diff --git a/deploy/coolify/docker-compose.yml b/deploy/coolify/docker-compose.yml
index c2f93ab9b..b34823dbc 100644
--- a/deploy/coolify/docker-compose.yml
+++ b/deploy/coolify/docker-compose.yml
@@ -4,7 +4,7 @@ services:
context: .
dockerfile: Dockerfile
args:
- AGENTMEMORY_VERSION: "0.9.28"
+ AGENTMEMORY_VERSION: "0.9.29"
III_VERSION: "0.11.2"
III_SDK_VERSION: "0.11.2"
restart: unless-stopped
diff --git a/deploy/fly/Dockerfile b/deploy/fly/Dockerfile
index 51da03a47..e09469988 100644
--- a/deploy/fly/Dockerfile
+++ b/deploy/fly/Dockerfile
@@ -4,7 +4,7 @@ FROM iiidev/iii:${III_VERSION} AS iii-image
FROM node:22-slim
-ARG AGENTMEMORY_VERSION=0.9.28
+ARG AGENTMEMORY_VERSION=0.9.29
ARG III_VERSION=0.11.2
ARG III_SDK_VERSION=0.11.2
diff --git a/deploy/railway/Dockerfile b/deploy/railway/Dockerfile
index 51da03a47..e09469988 100644
--- a/deploy/railway/Dockerfile
+++ b/deploy/railway/Dockerfile
@@ -4,7 +4,7 @@ FROM iiidev/iii:${III_VERSION} AS iii-image
FROM node:22-slim
-ARG AGENTMEMORY_VERSION=0.9.28
+ARG AGENTMEMORY_VERSION=0.9.29
ARG III_VERSION=0.11.2
ARG III_SDK_VERSION=0.11.2
diff --git a/deploy/render/Dockerfile b/deploy/render/Dockerfile
index 51da03a47..e09469988 100644
--- a/deploy/render/Dockerfile
+++ b/deploy/render/Dockerfile
@@ -4,7 +4,7 @@ FROM iiidev/iii:${III_VERSION} AS iii-image
FROM node:22-slim
-ARG AGENTMEMORY_VERSION=0.9.28
+ARG AGENTMEMORY_VERSION=0.9.29
ARG III_VERSION=0.11.2
ARG III_SDK_VERSION=0.11.2
diff --git a/deploy/render/render.yaml b/deploy/render/render.yaml
index b2333805a..d1871d579 100644
--- a/deploy/render/render.yaml
+++ b/deploy/render/render.yaml
@@ -15,7 +15,7 @@ services:
- key: PORT
value: "3111"
- key: AGENTMEMORY_VERSION
- value: "0.9.28"
+ value: "0.9.29"
- key: III_VERSION
value: "0.11.2"
- key: III_SDK_VERSION
diff --git a/plugin/opencode/plugin.json b/plugin/opencode/plugin.json
index 1472752e9..cf06cee5a 100644
--- a/plugin/opencode/plugin.json
+++ b/plugin/opencode/plugin.json
@@ -1,6 +1,6 @@
{
"name": "agentmemory-capture",
- "version": "0.9.20",
+ "version": "0.9.29",
"description": "OpenCode plugin for agentmemory — full Claude Code hook parity: session lifecycle (create/idle/status/compacted/update/diff/delete/error), messages & prompts (chat.message, message.updated user+assistant, message.removed), tool lifecycle (ToolPart states with timing), part tracking (subtask, step-finish, reasoning, file, patch, compaction, agent, retry), file enrichment pipeline, permissions, task tracking (w/ priority), commands, config & model tracking. 22 hooks, 2 slash commands.",
"author": {
"name": "Rohit Ghumare",
diff --git a/website/lib/generated-meta.json b/website/lib/generated-meta.json
index 06792f8e5..8ebf1fd05 100644
--- a/website/lib/generated-meta.json
+++ b/website/lib/generated-meta.json
@@ -1,8 +1,8 @@
{
- "version": "0.9.28",
- "mcpTools": 53,
+ "version": "0.9.29",
+ "mcpTools": 54,
"hooks": 12,
- "restEndpoints": 128,
- "testsPassing": 1428,
- "generatedAt": "2026-07-19T10:24:26.108Z"
+ "restEndpoints": 130,
+ "testsPassing": 1610,
+ "generatedAt": "2026-08-09T09:15:51.690Z"
}
From 52f4178532cbe31ccd19f88e0e0ff7a3e755cb3b Mon Sep 17 00:00:00 2001
From: Rohit Ghumare
Date: Sun, 9 Aug 2026 10:26:51 +0100
Subject: [PATCH 11/12] chore(release): sync stat-tests badge to 1596+ and
commit bridge exec bit
---
assets/tags/light/stat-tests.svg | 4 ++--
assets/tags/stat-tests.svg | 4 ++--
plugin/scripts/antigravity-bridge.mjs | 0
3 files changed, 4 insertions(+), 4 deletions(-)
mode change 100644 => 100755 plugin/scripts/antigravity-bridge.mjs
diff --git a/assets/tags/light/stat-tests.svg b/assets/tags/light/stat-tests.svg
index f161f25f4..b8f386db0 100644
--- a/assets/tags/light/stat-tests.svg
+++ b/assets/tags/light/stat-tests.svg
@@ -1,5 +1,5 @@
-
+
- 1550+
+ 1596+TESTS PASSING
diff --git a/assets/tags/stat-tests.svg b/assets/tags/stat-tests.svg
index 35eb29b73..8a4637dde 100644
--- a/assets/tags/stat-tests.svg
+++ b/assets/tags/stat-tests.svg
@@ -1,5 +1,5 @@
-
+
- 1550+
+ 1596+TESTS PASSING
diff --git a/plugin/scripts/antigravity-bridge.mjs b/plugin/scripts/antigravity-bridge.mjs
old mode 100644
new mode 100755
From 452c3ce957cc42487b840b5a4bc2d22885131ac7 Mon Sep 17 00:00:00 2001
From: Rohit Ghumare
Date: Sun, 9 Aug 2026 10:44:42 +0100
Subject: [PATCH 12/12] refactor: trim frame-guard comments and drop issue refs
from code
---
src/functions/export-import.ts | 6 ++----
src/state/frame-guard.ts | 19 ++++++-------------
src/triggers/api.ts | 4 +---
test/frame-guard.test.ts | 8 +++-----
test/mesh-export-project-scope.test.ts | 12 ++++--------
5 files changed, 16 insertions(+), 33 deletions(-)
diff --git a/src/functions/export-import.ts b/src/functions/export-import.ts
index 0385064c5..23854a97f 100644
--- a/src/functions/export-import.ts
+++ b/src/functions/export-import.ts
@@ -182,10 +182,8 @@ export function registerExportImportFunction(sdk: ISdk, kv: StateKV): void {
summaries: summaries.length,
});
- // Refuse an over-frame payload here rather than let it die on the
- // worker->engine hop and drop the whole daemon (#1142). The session
- // collections page on ?maxSessions/?offset, but the other collections
- // do not, so a large store can exceed the cap even at ?maxSessions=1.
+ // Only session collections page on ?maxSessions/?offset, so a large
+ // store can exceed the transport cap even at ?maxSessions=1.
const oversized = checkPayloadFrameSize(
exportData,
"narrow the range with ?maxSessions / ?offset, or export fewer collections; the non-session collections (memories, graph, semantic, actions, lessons, ...) are not yet paginated",
diff --git a/src/state/frame-guard.ts b/src/state/frame-guard.ts
index e9a1f76c6..8651b384d 100644
--- a/src/state/frame-guard.ts
+++ b/src/state/frame-guard.ts
@@ -1,13 +1,7 @@
-// The pinned iii engine (0.11.2) refuses any WebSocket frame larger than
-// tungstenite's 16 MiB default (max_frame_size). A function result or HTTP
-// response body that serializes past this dies on the worker->engine hop,
-// which drops and re-registers the worker and 404s every endpoint for ~1s
-// (issue #1142, and the mesh/export twin #890). We cannot raise the engine
-// limit without moving off the pin, so oversized payloads must be refused as
-// one clean request instead of being shipped and taking the daemon down.
-//
-// The cap sits below the frame limit to leave headroom for the SDK's own
-// framing/serialization overhead on top of the raw JSON we measure here.
+// The pinned engine rejects WebSocket frames over 16 MiB; an oversized
+// function result drops the worker and 404s every endpoint. Refuse the
+// payload as one clean error instead. The cap sits under the frame limit
+// to leave headroom for the SDK's framing overhead.
const FRAME_LIMIT_BYTES = 16 * 1024 * 1024;
export const SAFE_PAYLOAD_BYTES = 15 * 1024 * 1024;
@@ -37,9 +31,8 @@ export function oversizedPayloadError(
};
}
-// Returns the oversized error when the payload would exceed the safe cap,
-// otherwise null. Serializes once; callers that also return the payload pay a
-// second serialization, which is acceptable on these cold export paths.
+// Serializes once; callers that also return the payload pay a second
+// serialization, acceptable on these cold export paths.
export function checkPayloadFrameSize(
payload: unknown,
hint: string,
diff --git a/src/triggers/api.ts b/src/triggers/api.ts
index 943910b29..7560e873d 100644
--- a/src/triggers/api.ts
+++ b/src/triggers/api.ts
@@ -2791,9 +2791,7 @@ export function registerApiTriggers(
);
body.graphEdges = df(graphEdges, "createdAt");
}
- // Same 16 MiB transport-frame ceiling as #1142: a full mesh export past
- // the frame limit would drop the worker (#890). Fail this one request
- // with 413 instead of taking the daemon down.
+ // Fail an over-frame export with 413 instead of dropping the worker.
const oversized = checkPayloadFrameSize(
body,
"use ?since to fetch only changes after a timestamp, or ?project to scope the export",
diff --git a/test/frame-guard.test.ts b/test/frame-guard.test.ts
index 5072817e6..fbb73461c 100644
--- a/test/frame-guard.test.ts
+++ b/test/frame-guard.test.ts
@@ -15,10 +15,8 @@ import { registerExportImportFunction } from "../src/functions/export-import.js"
import { KV } from "../src/state/schema.js";
import type { Session } from "../src/types.js";
-// The pinned iii engine refuses a WebSocket frame over 16 MiB; a function
-// result that serializes larger dies on the worker->engine hop and drops the
-// worker, 404ing every endpoint (#1142). The guard must catch it BEFORE the
-// return so the oversized frame is never shipped.
+// The guard must catch an oversized payload before the return so the frame
+// that would drop the worker is never shipped.
describe("frame-guard", () => {
it("keeps the safe cap under the 16 MiB frame limit with headroom", () => {
@@ -82,7 +80,7 @@ function mockSdk(kv: ReturnType) {
} as never;
}
-describe("mem::export frame guard (#1142)", () => {
+describe("mem::export frame guard", () => {
it("returns the export object when it fits under the frame limit", async () => {
const kv = mockKV();
await kv.set(KV.sessions, "s1", {
diff --git a/test/mesh-export-project-scope.test.ts b/test/mesh-export-project-scope.test.ts
index b64e06bcc..7ff72c7e1 100644
--- a/test/mesh-export-project-scope.test.ts
+++ b/test/mesh-export-project-scope.test.ts
@@ -9,13 +9,9 @@ import { KV } from "../src/state/schema.js";
import { SAFE_PAYLOAD_BYTES } from "../src/state/frame-guard.js";
import type { Memory } from "../src/types.js";
-// api::mesh-export filters `actions` by ?project but historically returned
-// ALL memories regardless of project. On a mesh instance federating one
-// project to a peer that only shares that project, the peer pulled every
-// other project's memories too (cross-project leak), and those extra memories
-// could push the payload past the 16 MiB transport frame into a 413 (#1142/#890)
-// even when the requested project's own slice fit fine. Memories carry the same
-// optional `project` field as actions, so the scoped export must filter both.
+// A project-scoped mesh export must filter memories like actions: unscoped
+// memories leak across projects and can push the payload past the transport
+// frame limit even when the requested project's own slice fits.
const SECRET = "mesh-test-secret";
@@ -75,7 +71,7 @@ async function meshExport(
});
}
-describe("api::mesh-export project scoping (#1142/#890)", () => {
+describe("api::mesh-export project scoping", () => {
it("excludes other projects' memories from a project-scoped export", async () => {
const kv = mockKV();
await kv.set(KV.memories, "m-alpha", memory("m-alpha", "alpha"));