specs/memory-api.v0.md
Memory API v0
Status: Draft
This document defines the human-readable v0 API contract for Infinicon. Machine-readable schemas should be generated after this draft is reviewed and accepted.
API Principles
- Every operation must be scoped.
- Writes must be idempotent where retry is expected.
- Queries should return memory references before hydrated content.
- Context assembly is first-class.
- Derived memory must preserve provenance.
- Deletion must be explicit and observable.
- Async operations must expose job state.
Common Concepts
Scope
All requests must include a scope.
At minimum:
tenantIdnamespaceId
Optional:
agentIdsessionId- domain-specific filters
MemoryRef
A memory ref identifies memory without requiring full content hydration.
Illustrative shape:
type MemoryRef = {
id: string
type: "episode" | "atom" | "consolidation" | "link"
scope: Scope
}
MemoryEventType
Event type strings used by subscribe:
type MemoryEventType =
| "episode.ingested"
| "atom.extracted"
| "memory.indexed"
| "consolidation.started"
| "consolidation.completed"
| "consolidation.failed"
| "memory.disputed"
| "memory.superseded"
| "memory.tombstoned"
MemoryEvent
Lifecycle event shape emitted by subscribe:
type MemoryEvent = {
type: MemoryEventType
scope: Scope
cursor: string
occurredAt: string
ref?: MemoryRef
jobId?: string
warnings?: ContextWarning[]
metadata?: Record<string, unknown>
}
Error Shape
Errors should be structured:
type MemoryApiError = {
code: string
message: string
retryable: boolean
details?: Record<string, unknown>
}
Operation: ingest
Writes one or more episodes into memory.
Request
type IngestRequest = {
scope: Scope
episodes: IngestEpisode[]
consistency?: "accepted" | "indexed"
}
Each episode should include:
contentTypecontentdedupeKeycreatedBymetadata
Response
type IngestResponse = {
results: {
ref: MemoryRef
status: "created" | "deduplicated" | "rejected"
error?: MemoryApiError
}[]
}
Semantics
- If a dedupe key matches a previous ingest in the same scope, the runtime must return the original ref.
consistency: "accepted"means the episode is durable but may not be indexed yet.consistency: "indexed"means the runtime should not return until the episode is visible to query, or until a timeout/error occurs.- Extraction and consolidation may happen asynchronously.
Operation: query
Returns ranked memory references matching a query.
Request
type QueryRequest = {
scope: Scope
query: string
filters?: MemoryFilters
limit?: number
consistency?: "strong" | "eventual"
}
Response
type QueryResponse = {
refs: {
ref: MemoryRef
score: number
reason?: string
warnings?: ContextWarning[]
}[]
cursor?: string
}
Semantics
- Query returns refs, not full memory content.
- The score is comparable only within the same query response unless a ranker declares stronger semantics.
- Tombstoned memory must not be returned.
- Disputed and superseded memory may be included only when filters allow it.
Operation: hydrate
Loads full memory objects for refs.
Request
type HydrateRequest = {
scope: Scope
refs: MemoryRef[]
includeProvenance?: boolean
}
Response
type HydrateResponse = {
objects: DurableMemoryObject[]
missing: MemoryRef[]
}
Semantics
- Hydrate must enforce scope and authorization.
- Missing refs must be reported rather than silently ignored.
- Tombstoned content must not be returned unless the caller has explicit audit permission.
Operation: assembleContext
Assembles task-specific working context within a budget.
This is the primary agent-facing operation.
Request
type AssembleContextRequest = {
scope: Scope
task: string
budget: ContextBudget
filters?: MemoryFilters
constraints?: ContextConstraints
consistency?: "strong" | "eventual"
}
Response
type AssembleContextResponse = {
context: WorkingContext
}
Semantics
- The runtime selects, ranks, hydrates, truncates, and orders memory segments.
- The response must include
tokenEstimate,truncated, and warnings. - The response must preserve segment provenance.
- Empty memory must return an explicit empty working context, not synthesized filler.
- The runtime should prefer active, relevant, fresh memory unless constraints say otherwise.
Budget
The context budget should support:
- Maximum token estimate.
- Optional maximum segment count.
- Optional reserved tokens for system or user prompt material.
Token estimates are estimates. The runtime should expose tokenizer assumptions when available.
Operation: consolidate
Requests or schedules consolidation for a scope.
Request
type ConsolidateRequest = {
scope: Scope
trigger: "manual" | "scheduled" | "threshold" | "idle"
filters?: MemoryFilters
mode?: "enqueue" | "run_now"
}
Response
type ConsolidateResponse = {
jobId: string
status: "queued" | "running" | "completed" | "failed"
}
Semantics
- Consolidation may be asynchronous.
- The runtime owns job state and provenance.
- Consolidator plugins own synthesis behavior.
- Failed consolidation must not corrupt existing active memory.
Operation: tombstone
Deletes or invalidates memory by creating tombstones.
Request
type TombstoneRequest = {
scope: Scope
refs: MemoryRef[]
reason: string
cascadePolicy: "none" | "mark_derived_stale" | "tombstone_derived"
}
Response
type TombstoneResponse = {
results: {
ref: MemoryRef
status: "tombstoned" | "already_tombstoned" | "not_found" | "rejected"
affectedDerivedRefs?: MemoryRef[]
error?: MemoryApiError
}[]
}
Semantics
- Tombstoned memory must be excluded from normal query and context assembly.
- Indexes must remove or hide tombstoned content.
- Derived memory handling depends on cascade policy.
- The runtime must prevent tombstoned content from reappearing through stale indexes.
Operation: subscribe
Subscribes to memory lifecycle events.
Request
type SubscribeRequest = {
scope: Scope
eventTypes?: MemoryEventType[]
cursor?: string
consistency?: "strong" | "eventual"
}
Event Types
Initial event types:
episode.ingestedatom.extractedmemory.indexedconsolidation.startedconsolidation.completedconsolidation.failedmemory.disputedmemory.supersededmemory.tombstoned
Response
type SubscribeResponse = {
events: MemoryEvent[]
nextCursor?: string
}
Semantics
- Subscriptions are for lifecycle awareness, not distributed consensus.
- Event delivery is at-least-once. Consumers must tolerate duplicate events.
- The runtime should preserve cursor order within a scope.
- Events should include a cursor or offset when transport supports it.
- If a
cursoris provided, the runtime should resume at or after that cursor. - If the runtime cannot resume from the provided cursor, it must fail with a structured error rather than silently skipping ahead.
consistency: "strong"should avoid gaps before the first returned event after a valid cursor.consistency: "eventual"may return delayed lifecycle events for lower-latency reads.- Events must be scoped and authorized like other memory operations.
- Subscriptions must not include tombstoned memory content in event payloads.
Failure Modes
Examples:
invalid_cursorwhen a supplied cursor is unknown, expired, or malformed.scope_mismatchwhen a cursor does not belong to the requested scope.consistency_not_supportedwhenstrongsubscription consistency is requested but unsupported by the adapter.unauthorizedwhen caller auth does not permit event subscription for the scope.
The runtime should use structured errors and avoid silently dropping events.
Operation: getJob
Fetches asynchronous job state.
Request
type GetJobRequest = {
scope: Scope
jobId: string
}
Response
type GetJobResponse = {
jobId: string
type: string
status: "queued" | "running" | "completed" | "failed" | "cancelled"
createdAt: string
updatedAt: string
result?: unknown
error?: MemoryApiError
}
Compatibility
Breaking changes to this API require a new major spec version.
Non-breaking changes may add optional fields, new event types, new warning codes, or new plugin-declared capabilities.
Open Questions
- Should v0 expose REST, gRPC, or both?
- Should query and assemble support streaming partial results?
- Should tombstone be synchronous by default or always job-backed?
- How should pagination work across heterogeneous storage adapters?
- Should
subscribebe long-lived streaming only, polling only, or transport-dependent?