Builds on merged PR-1..7 (PR-8 in queue). Pure docs; no code.
What ships:
* docs/memory-plugins/README.md — contract overview, capability
negotiation, deployment models, replacement workflow
* docs/memory-plugins/testing-your-plugin.md — using the contract
test harness to validate wire compatibility, what the harness
DOES NOT cover (capability accuracy, TTL eviction, concurrency)
* docs/memory-plugins/pinecone-example/README.md — worked example
of a Pinecone-backed plugin: capability mapping (only embedding,
no FTS), wire mapping (memory → vector + metadata), production-
hardening checklist
Documentation strategy:
* Lead with what workspace-server takes care of (security perimeter,
redaction, ACL, GLOBAL audit, prompt-injection wrap) so plugin
authors don't reimplement those layers
* Show three deployment models (same machine / separate container /
self-managed) so operators see their topology
* Capability table makes it explicit what each capability gates so
a plugin that supports only one (e.g. semantic search) is still
a useful plugin
* Pinecone example is honest: shows the skeleton, the wire mapping,
and explicitly calls out what's MISSING from the sketch (batch
commits, TTL janitor, circuit breaker, metrics)
3.5 KiB
Testing Your Memory Plugin
Once you have a plugin implementing the v1 contract, you can validate it against the spec without booting workspace-server.
The contract test harness
Workspace-server ships typed Go bindings + round-trip tests in
workspace-server/internal/memory/contract/. The simplest way to
gain confidence in your plugin's wire compatibility is to point those
tests at it.
A minimal contract suite:
package myplugin_test
import (
"context"
"testing"
mclient "github.com/Molecule-AI/molecule-monorepo/platform/internal/memory/client"
"github.com/Molecule-AI/molecule-monorepo/platform/internal/memory/contract"
)
func TestMyPlugin_FullRoundTrip(t *testing.T) {
// Start your plugin somehow (subprocess, in-process, etc.)
pluginURL := startMyPlugin(t)
cl := mclient.New(mclient.Config{BaseURL: pluginURL})
// 1. Health
hr, err := cl.Boot(context.Background())
if err != nil {
t.Fatalf("Boot: %v", err)
}
if hr.Status != "ok" {
t.Errorf("status = %q", hr.Status)
}
// 2. Namespace upsert
if _, err := cl.UpsertNamespace(context.Background(), "workspace:test-1",
contract.NamespaceUpsert{Kind: contract.NamespaceKindWorkspace}); err != nil {
t.Fatalf("UpsertNamespace: %v", err)
}
// 3. Commit memory
resp, err := cl.CommitMemory(context.Background(), "workspace:test-1",
contract.MemoryWrite{
Content: "hello",
Kind: contract.MemoryKindFact,
Source: contract.MemorySourceAgent,
})
if err != nil {
t.Fatalf("CommitMemory: %v", err)
}
if resp.ID == "" {
t.Errorf("plugin must return a non-empty memory id")
}
// 4. Search
sresp, err := cl.Search(context.Background(), contract.SearchRequest{
Namespaces: []string{"workspace:test-1"},
Query: "hello",
})
if err != nil {
t.Fatalf("Search: %v", err)
}
if len(sresp.Memories) == 0 {
t.Errorf("plugin returned no memories for the query we just wrote")
}
// 5. Forget
if err := cl.ForgetMemory(context.Background(), resp.ID,
contract.ForgetRequest{RequestedByNamespace: "workspace:test-1"}); err != nil {
t.Errorf("ForgetMemory: %v", err)
}
}
What the harness does NOT cover
- Capability accuracy: if you list
embeddingyou must actually do semantic search. The harness can't tell you whether ranking is meaningful — only that you don't crash. - TTL eviction: write a memory with
expires_at1 second in the future, sleep 2 seconds, search — assert the memory is gone. - Concurrency: hit your plugin with 100 parallel writes; assert no IDs collide.
- Recovery: kill your plugin's storage backend, send a request, assert your plugin returns 503 (not 200 with stale data).
Smoke test against workspace-server
Once unit-level wire tests pass, run a real workspace-server with your plugin URL:
DATABASE_URL=postgres://... \
MEMORY_PLUGIN_URL=http://localhost:9100 \
./workspace-server
Then ask an agent to call commit_memory_v2 and search_memory. If
both round-trip cleanly, you're done.
For the full E2E flow (including the namespace resolver, MCP layer, and security perimeter), see PR-11's plugin-swap test.
Reporting bugs
If you find a contract ambiguity or missing edge case, file an issue
against Molecule-AI/molecule-core referencing RFC #2728.