diff --git a/.changeset/lazy-poets-relax.md b/.changeset/lazy-poets-relax.md
new file mode 100644
index 00000000..36650915
--- /dev/null
+++ b/.changeset/lazy-poets-relax.md
@@ -0,0 +1,5 @@
+---
+'@rushdb/agent-memory-contract': minor
+---
+
+Fix CI for memory agent contract
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index d0fba479..382ba229 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -18,7 +18,6 @@ concurrency:
# ✓ Build & Type-check
# ✓ Lint
# ✓ Test
-# ✓ Docs Build
# ──────────────────────────────────────────────────────────────────────────────
jobs:
@@ -46,6 +45,12 @@ jobs:
- name: Build javascript-sdk
run: pnpm --filter ./packages/javascript-sdk build
+ - name: Build agent-memory-contract
+ run: pnpm --filter ./packages/agent-memory-contract build
+
+ - name: Verify agent-memory-contract package
+ run: pnpm --filter ./packages/agent-memory-contract pack:check
+
- name: Build mcp-server
run: pnpm --filter ./packages/mcp-server build
@@ -60,9 +65,6 @@ jobs:
- name: Type-check javascript-sdk
run: pnpm --filter ./packages/javascript-sdk types:check
- - name: Type-check docs
- run: pnpm --filter ./docs types:check
-
# ── ESLint across all packages that define a lint script ────────────────────
lint:
name: Lint
@@ -116,30 +118,11 @@ jobs:
- name: Build javascript-sdk
run: pnpm --filter ./packages/javascript-sdk build
+ - name: Test agent-memory-contract
+ run: pnpm --filter ./packages/agent-memory-contract test
+
# 9 pure unit tests for the Cypher query parser — no DB or network required.
- name: Test platform/core (unit)
run: pnpm --filter ./platform/core test
env:
NODE_ENV: test
-
- # ── Docusaurus build (validates all internal links and MDX syntax) ───────────
- docs:
- name: Docs Build
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
-
- - uses: pnpm/action-setup@v4
- with:
- version: 10.1.0
-
- - uses: actions/setup-node@v4
- with:
- node-version: 22
- cache: pnpm
-
- - name: Install dependencies
- run: pnpm install --frozen-lockfile
-
- - name: Build docs
- run: pnpm --filter ./docs build
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 861b23f4..b58c8fb8 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -93,7 +93,10 @@ jobs:
run: pnpm install --frozen-lockfile
- name: Build packages
- run: pnpm --filter ./packages/javascript-sdk... --filter ./packages/mcp-server... build
+ run: pnpm --filter ./packages/javascript-sdk... --filter ./packages/agent-memory-contract... --filter ./packages/mcp-server... build
+
+ - name: Verify agent-memory-contract package
+ run: pnpm --filter ./packages/agent-memory-contract pack:check
- name: Setup .npmrc
run: |
@@ -106,7 +109,7 @@ jobs:
# Opens/updates the "version packages" PR when changesets are pending,
# publishes to npm when the versioned commit lands. `changeset publish`
- # covers every public workspace package (sdk, mcp-server, skills).
+ # covers every public workspace package (sdk, memory contract, mcp-server, skills).
- name: Create Release PR or Publish
id: changesets
if: github.event_name == 'push'
diff --git a/package.json b/package.json
index f096ddb9..e96a8730 100644
--- a/package.json
+++ b/package.json
@@ -62,8 +62,7 @@
},
"workspaces": [
"packages/*",
- "platform/*",
- "docs"
+ "platform/*"
],
"dependencies": {
"npm-run-all": "^4.1.5"
diff --git a/packages/agent-memory-contract/README.md b/packages/agent-memory-contract/README.md
new file mode 100644
index 00000000..44085f7d
--- /dev/null
+++ b/packages/agent-memory-contract/README.md
@@ -0,0 +1,164 @@
+
+
+
+
+# RushDB Agent Memory Contract
+
+### One memory protocol for OpenClaw, Hermes, and custom agent runtimes.
+
+[](https://www.npmjs.com/package/@rushdb/agent-memory-contract)
+[](#license)
+
+[Website](https://rushdb.com) · [Documentation](https://docs.rushdb.com) · [RushDB Cloud](https://app.rushdb.com)
+
+
+
+---
+
+`@rushdb/agent-memory-contract` is the provider-neutral TypeScript boundary for durable agent memory
+in RushDB. It gives runtime adapters a shared event model, deterministic identities, strict scope
+filters, semantic recall primitives, and language-neutral conformance resources.
+
+Use it when building a native memory integration for an agent runtime. It keeps stored episodes and
+facts compatible across adapters without forcing runtimes to share lifecycle code.
+
+## What it provides
+
+- `AgentMemoryEvent v1`, with `EPISODE` and `MEMORY_FACT` variants
+- deterministic SHA-256 `eventId` and `factId` generation
+- participant-scope hashing and complete authorization query filters
+- idempotent RushDB upserts for episodes and facts
+- semantic recall across episode summaries and active facts
+- a bounded recent-write cache for recall before embeddings become visible
+- formatting that labels recalled memory as untrusted historical context
+- a published JSON Schema and cross-language conformance fixture
+
+## Installation
+
+```bash
+pnpm add @rushdb/agent-memory-contract
+```
+
+## Quick start
+
+```typescript
+import {
+ RushDBAgentMemory,
+ createEpisodeEvent,
+ formatRecalledMemories,
+ hashScope
+} from '@rushdb/agent-memory-contract'
+
+const scope = {
+ agentId: 'support-agent',
+ profileId: 'default',
+ privacyScope: 'private' as const,
+ participantScopeHash: hashScope(['discord', 'account-1', 'user-42'], process.env.MEMORY_SCOPE_SALT),
+ sandboxEligible: false
+}
+
+const memory = new RushDBAgentMemory({
+ apiKey: process.env.RUSHDB_API_KEY
+})
+
+await memory.ensureIndexes()
+
+await memory.persistEpisode(
+ createEpisodeEvent({
+ ...scope,
+ runtime: 'custom',
+ externalSessionId: 'session-42',
+ sourceEventId: 'turn-7',
+ turnIndex: 7,
+ userText: 'Prefer TypeScript for this service.',
+ assistantText: 'I will use TypeScript.',
+ summary: 'The user prefers TypeScript for this service.',
+ conversationKind: 'direct',
+ visibility: 'participant',
+ trustClass: 'mixed',
+ originClass: 'conversation',
+ observedAt: new Date().toISOString(),
+ provenance: 'custom:completed_turn'
+ })
+)
+
+const recalled = await memory.recall({
+ ...scope,
+ query: 'Which language does the user prefer?',
+ excludeSessionId: 'session-42',
+ limit: 6
+})
+
+const context = formatRecalledMemories(recalled)
+```
+
+`ensureIndexes()` creates managed embedding indexes for `EPISODE.summary` and
+`MEMORY_FACT.text` when they do not already exist.
+
+## Event model
+
+| Event | RushDB label | Purpose |
+| -------------- | ------------- | ---------------------------------------------------------- |
+| Episode | `EPISODE` | A bounded, completed user/assistant interaction |
+| Canonical fact | `MEMORY_FACT` | A durable fact produced by an explicit, trusted write path |
+
+Every event includes the authorization scope:
+
+```text
+agentId
+profileId
+privacyScope
+participantScopeHash
+sandboxEligible
+```
+
+Recall applies all five fields before similarity ranking. Fact recall additionally requires
+`active: true`; episode recall can exclude the current external session.
+
+Derive scope only from trusted host metadata. Never accept agent, participant, privacy, or sandbox
+scope from model output or prompt text. Use separate RushDB projects when hard tenant isolation is
+required.
+
+## Package exports
+
+| Export | Contents |
+| ---------------------------------------- | ------------------------------------------------------ |
+| `@rushdb/agent-memory-contract` | TypeScript types, canonical helpers, client, formatter |
+| `@rushdb/agent-memory-contract/schema` | AgentMemoryEvent v1 JSON Schema |
+| `@rushdb/agent-memory-contract/fixtures` | Deterministic cross-language conformance fixture |
+
+Other languages should implement the published schema and produce the same IDs as the conformance
+fixture.
+
+## Adapter responsibilities
+
+This package is a protocol and client primitive, not a complete runtime adapter. The host
+integration remains responsible for:
+
+- proving a conversation is eligible before deriving scope
+- capturing only the latest successful, bounded user/assistant pair
+- excluding system prompts, tool transcripts, secrets, command output, and local paths
+- applying a short fail-open timeout to recall
+- writing to a durable outbox before background persistence
+- retrying and replaying pending writes without losing idempotency
+- deactivating a prior fact when publishing a replacement via `supersedesFactId`
+- clearing session state and performing bounded flushes on session end and shutdown
+
+Treat formatted recall as historical data, never as instructions or policy.
+
+## Development
+
+From the RushDB monorepo root:
+
+```bash
+pnpm --filter ./packages/agent-memory-contract types:check
+pnpm --filter ./packages/agent-memory-contract test
+pnpm --filter ./packages/agent-memory-contract pack:check
+```
+
+`pack:check` builds the package and verifies that the npm tarball contains the README, runtime
+JavaScript, TypeScript declarations, JSON Schema, and conformance fixture.
+
+## License
+
+Apache-2.0
diff --git a/packages/agent-memory-contract/package.json b/packages/agent-memory-contract/package.json
index e14b00ab..55ac1649 100644
--- a/packages/agent-memory-contract/package.json
+++ b/packages/agent-memory-contract/package.json
@@ -15,12 +15,15 @@
"./fixtures": "./fixtures/conformance.v1.json"
},
"files": [
+ "README.md",
"dist",
"schema",
"fixtures"
],
"scripts": {
"build": "rimraf dist && tsc",
+ "pack:check": "node scripts/verify-package.mjs",
+ "prepack": "pnpm build",
"types:check": "tsc --noEmit",
"test": "vitest run"
},
diff --git a/packages/agent-memory-contract/scripts/verify-package.mjs b/packages/agent-memory-contract/scripts/verify-package.mjs
new file mode 100644
index 00000000..76924ab7
--- /dev/null
+++ b/packages/agent-memory-contract/scripts/verify-package.mjs
@@ -0,0 +1,51 @@
+import { spawnSync } from 'node:child_process'
+import { mkdtempSync, readdirSync, rmSync } from 'node:fs'
+import { tmpdir } from 'node:os'
+import { dirname, join } from 'node:path'
+import { fileURLToPath } from 'node:url'
+
+const packageDirectory = dirname(dirname(fileURLToPath(import.meta.url)))
+const packDirectory = mkdtempSync(join(tmpdir(), 'rushdb-agent-memory-contract-'))
+const requiredFiles = [
+ 'package/README.md',
+ 'package/dist/index.js',
+ 'package/dist/index.d.ts',
+ 'package/schema/agent-memory-event.v1.schema.json',
+ 'package/fixtures/conformance.v1.json'
+]
+
+function run(command, args, options = {}) {
+ const result = spawnSync(command, args, {
+ cwd: packageDirectory,
+ encoding: 'utf8',
+ ...options
+ })
+ if (result.status !== 0) {
+ process.stderr.write(result.stdout)
+ process.stderr.write(result.stderr)
+ process.exit(result.status ?? 1)
+ }
+ return result.stdout
+}
+
+try {
+ run('npm', ['pack', '--pack-destination', packDirectory], {
+ env: { ...process.env, npm_config_cache: join(packDirectory, 'npm-cache') }
+ })
+ const tarball = readdirSync(packDirectory).find((entry) => entry.endsWith('.tgz'))
+ if (!tarball) throw new Error('npm pack did not create a tarball')
+
+ const files = new Set(
+ run('tar', ['-tzf', join(packDirectory, tarball)])
+ .trim()
+ .split('\n')
+ )
+ const missing = requiredFiles.filter((file) => !files.has(file))
+ if (missing.length > 0) {
+ throw new Error(`Agent memory contract package is missing: ${missing.join(', ')}`)
+ }
+
+ console.log(`Verified ${tarball}: ${requiredFiles.length} required files are present`)
+} finally {
+ rmSync(packDirectory, { recursive: true, force: true })
+}