From 696765a0be2141ec2c4b0ceaca171175e2709623 Mon Sep 17 00:00:00 2001 From: Artemiy Vereshchinskiy Date: Tue, 18 Aug 2026 22:01:49 +0700 Subject: [PATCH] Fix CI for memory agent contract --- .changeset/lazy-poets-relax.md | 5 + .github/workflows/ci.yml | 35 +--- .github/workflows/release.yml | 7 +- package.json | 3 +- packages/agent-memory-contract/README.md | 164 ++++++++++++++++++ packages/agent-memory-contract/package.json | 3 + .../scripts/verify-package.mjs | 51 ++++++ 7 files changed, 238 insertions(+), 30 deletions(-) create mode 100644 .changeset/lazy-poets-relax.md create mode 100644 packages/agent-memory-contract/README.md create mode 100644 packages/agent-memory-contract/scripts/verify-package.mjs 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 Logo](https://raw.githubusercontent.com/rush-db/rushdb/main/rushdb-logo.svg) + +# RushDB Agent Memory Contract + +### One memory protocol for OpenClaw, Hermes, and custom agent runtimes. + +[![npm](https://img.shields.io/npm/v/%40rushdb%2Fagent-memory-contract)](https://www.npmjs.com/package/@rushdb/agent-memory-contract) +[![license](https://img.shields.io/npm/l/%40rushdb%2Fagent-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 }) +}