12 KiB
Paperclip Memory Service Plan
Goal
Define a Paperclip memory service and surface API that can sit above multiple memory backends, while preserving Paperclip's control-plane requirements:
- company scoping
- auditability
- provenance back to Paperclip work objects
- budget / cost visibility
- plugin-first extensibility
This plan is based on the external landscape summarized in doc/memory-landscape.md and on the current Paperclip architecture in:
doc/SPEC-implementation.mddoc/plugins/PLUGIN_SPEC.mddoc/plugins/PLUGIN_AUTHORING_GUIDE.mdpackages/plugins/sdk/src/types.ts
Recommendation In One Sentence
Paperclip should not embed one opinionated memory engine into core. It should add a company-scoped memory control plane with a small normalized adapter contract, then let built-ins and plugins implement the provider-specific behavior.
Product Decisions
1. Memory is company-scoped by default
Every memory binding belongs to exactly one company.
That binding can then be:
- the company default
- an agent override
- a project override later if we need it
No cross-company memory sharing in the initial design.
2. Providers are selected by key
Each configured memory provider gets a stable key inside a company, for example:
defaultmem0-prodlocal-markdownresearch-kb
Agents and services resolve the active provider by key, not by hard-coded vendor logic.
3. Plugins are the primary provider path
Built-ins are useful for a zero-config local path, but most providers should arrive through the existing Paperclip plugin runtime.
That keeps the core small and matches the current direction that optional knowledge-like systems live at the edges.
4. Paperclip owns routing, provenance, and accounting
Providers should not decide how Paperclip entities map to governance.
Paperclip core should own:
- who is allowed to call a memory operation
- which company / agent / project scope is active
- what issue / run / comment / document the operation belongs to
- how usage gets recorded
5. Automatic memory should be narrow at first
Automatic capture is useful, but broad silent capture is dangerous.
Initial automatic hooks should be:
- post-run capture from agent runs
- issue comment / document capture when the binding enables it
- pre-run recall for agent context hydration
Everything else should start explicit.
Proposed Concepts
Memory provider
A built-in or plugin-supplied implementation that stores and retrieves memory.
Examples:
- local markdown + vector index
- mem0 adapter
- supermemory adapter
- MemOS adapter
Memory binding
A company-scoped configuration record that points to a provider and carries provider-specific config.
This is the object selected by key.
Memory scope
The normalized Paperclip scope passed into a provider request.
At minimum:
companyId- optional
agentId - optional
projectId - optional
issueId - optional
runId - optional
subjectIdfor external/user identity
Memory source reference
The provenance handle that explains where a memory came from.
Supported source kinds should include:
issue_commentissue_documentissuerunactivitymanual_noteexternal_document
Memory operation
A normalized write, query, browse, or delete action performed through Paperclip.
Paperclip should log every operation, whether the provider is local or external.
Required Adapter Contract
The required core should be small enough to fit memsearch, mem0, Memori, MemOS, or OpenViking.
export interface MemoryAdapterCapabilities {
profile?: boolean;
browse?: boolean;
correction?: boolean;
asyncIngestion?: boolean;
multimodal?: boolean;
providerManagedExtraction?: boolean;
}
export interface MemoryScope {
companyId: string;
agentId?: string;
projectId?: string;
issueId?: string;
runId?: string;
subjectId?: string;
}
export interface MemorySourceRef {
kind:
| "issue_comment"
| "issue_document"
| "issue"
| "run"
| "activity"
| "manual_note"
| "external_document";
companyId: string;
issueId?: string;
commentId?: string;
documentKey?: string;
runId?: string;
activityId?: string;
externalRef?: string;
}
export interface MemoryUsage {
provider: string;
model?: string;
inputTokens?: number;
outputTokens?: number;
embeddingTokens?: number;
costCents?: number;
latencyMs?: number;
details?: Record<string, unknown>;
}
export interface MemoryWriteRequest {
bindingKey: string;
scope: MemoryScope;
source: MemorySourceRef;
content: string;
metadata?: Record<string, unknown>;
mode?: "append" | "upsert" | "summarize";
}
export interface MemoryRecordHandle {
providerKey: string;
providerRecordId: string;
}
export interface MemoryQueryRequest {
bindingKey: string;
scope: MemoryScope;
query: string;
topK?: number;
intent?: "agent_preamble" | "answer" | "browse";
metadataFilter?: Record<string, unknown>;
}
export interface MemorySnippet {
handle: MemoryRecordHandle;
text: string;
score?: number;
summary?: string;
source?: MemorySourceRef;
metadata?: Record<string, unknown>;
}
export interface MemoryContextBundle {
snippets: MemorySnippet[];
profileSummary?: string;
usage?: MemoryUsage[];
}
export interface MemoryAdapter {
key: string;
capabilities: MemoryAdapterCapabilities;
write(req: MemoryWriteRequest): Promise<{
records?: MemoryRecordHandle[];
usage?: MemoryUsage[];
}>;
query(req: MemoryQueryRequest): Promise<MemoryContextBundle>;
get(handle: MemoryRecordHandle, scope: MemoryScope): Promise<MemorySnippet | null>;
forget(handles: MemoryRecordHandle[], scope: MemoryScope): Promise<{ usage?: MemoryUsage[] }>;
}
This contract intentionally does not force a provider to expose its internal graph, filesystem, or ontology.
Optional Adapter Surfaces
These should be capability-gated, not required:
browse(scope, filters)for file-system / graph / timeline inspectioncorrect(handle, patch)for natural-language correction flowsprofile(scope)when the provider can synthesize stable preferences or summariessync(source)for connectors or background ingestionexplain(queryResult)for providers that can expose retrieval traces
What Paperclip Should Persist
Paperclip should not mirror the full provider memory corpus into Postgres unless the provider is a Paperclip-managed local provider.
Paperclip core should persist:
- memory bindings and overrides
- provider keys and capability metadata
- normalized memory operation logs
- provider record handles returned by operations when available
- source references back to issue comments, documents, runs, and activity
- usage and cost data
For external providers, the memory payload itself can remain in the provider.
Hook Model
Automatic hooks
These should be low-risk and easy to reason about:
-
pre-run hydrateBefore an agent run starts, Paperclip may callquery(... intent = "agent_preamble")using the active binding. -
post-run captureAfter a run finishes, Paperclip may write a summary or transcript-derived note tied to the run. -
issue comment / document captureWhen enabled on the binding, Paperclip may capture selected issue comments or issue documents as memory sources.
Explicit hooks
These should be tool- or UI-driven first:
memory.searchmemory.notememory.forgetmemory.correctmemory.browse
Not automatic in the first version
- broad web crawling
- silent import of arbitrary repo files
- cross-company memory sharing
- automatic destructive deletion
- provider migration between bindings
Agent UX Rules
Paperclip should give agents both automatic recall and explicit tools, with simple guidance:
- use
memory.searchwhen the task depends on prior decisions, people, projects, or long-running context that is not in the current issue thread - use
memory.notewhen a durable fact, preference, or decision should survive this run - use
memory.correctwhen the user explicitly says prior context is wrong - rely on post-run auto-capture for ordinary session residue so agents do not have to write memory notes for every trivial exchange
This keeps memory available without forcing every agent prompt to become a memory-management protocol.
Browse And Inspect Surface
Paperclip needs a first-class UI for memory, otherwise providers become black boxes.
The initial browse surface should support:
- active binding by company and agent
- recent memory operations
- recent write sources
- query results with source backlinks
- filters by agent, issue, run, source kind, and date
- provider usage / cost / latency summaries
When a provider supports richer browsing, the plugin can add deeper views through the existing plugin UI surfaces.
Cost And Evaluation
Every adapter response should be able to return usage records.
Paperclip should roll up:
- memory inference tokens
- embedding tokens
- external provider cost
- latency
- query count
- write count
It should also record evaluation-oriented metrics where possible:
- recall hit rate
- empty query rate
- manual correction count
- per-binding success / failure counts
This is important because a memory system that "works" but silently burns budget is not acceptable in Paperclip.
Suggested Data Model Additions
At the control-plane level, the likely new core tables are:
-
memory_bindings- company-scoped key
- provider id / plugin id
- config blob
- enabled status
-
memory_binding_targets- target type (
company,agent, laterproject) - target id
- binding id
- target type (
-
memory_operations- company id
- binding id
- operation type (
write,query,forget,browse,correct) - scope fields
- source refs
- usage / latency / cost
- success / error
Provider-specific long-form state should stay in plugin state or the provider itself unless a built-in local provider needs its own schema.
Recommended First Built-In
The best zero-config built-in is a local markdown-first provider with optional semantic indexing.
Why:
- it matches Paperclip's local-first posture
- it is inspectable
- it is easy to back up and debug
- it gives the system a baseline even without external API keys
The design should still treat that built-in as just another provider behind the same control-plane contract.
Rollout Phases
Phase 1: Control-plane contract
- add memory binding models and API types
- add plugin capability / registration surface for memory providers
- add operation logging and usage reporting
Phase 2: One built-in + one plugin example
- ship a local markdown-first provider
- ship one hosted adapter example to validate the external-provider path
Phase 3: UI inspection
- add company / agent memory settings
- add a memory operation explorer
- add source backlinks to issues and runs
Phase 4: Automatic hooks
- pre-run hydrate
- post-run capture
- selected issue comment / document capture
Phase 5: Rich capabilities
- correction flows
- provider-native browse / graph views
- project-level overrides if needed
- evaluation dashboards
Open Questions
- Should project overrides exist in V1 of the memory service, or should we force company default + agent override first?
- Do we want Paperclip-managed extraction pipelines at all, or should built-ins be the only place where Paperclip owns extraction?
- Should memory usage extend the current
cost_eventsmodel directly, or should memory operations keep a parallel usage log and roll up intocost_eventssecondarily? - Do we want provider install / binding changes to require approvals for some companies?
Bottom Line
The right abstraction is:
- Paperclip owns memory bindings, scopes, provenance, governance, and usage reporting.
- Providers own extraction, ranking, storage, and provider-native memory semantics.
That gives Paperclip a stable "memory service" without locking the product to one memory philosophy or one vendor.