Memory
A Memory points Ark at an HTTP service that stores conversation history keyed by conversationId / queryId. The CRD is backend-agnostic — any service that implements the Memory HTTP API can back it (a database, queue, or other store). The in-cluster ark-broker is the reference implementation. Queries reference a Memory via spec.memory.name; when unset, resolution falls back to a Memory named default in the same namespace.
Spec
apiVersion: ark.mckinsey.com/v1alpha1
kind: Memory
metadata:
name: ark-broker
spec:
# --- Required ----------------------------------------------------------
address: # the memory service endpoint (ValueSource)
valueFrom:
serviceRef: # point at an in-cluster service
name: ark-broker
port: "8080"
# value: http://memory.example.com:8080 # OR a direct URL
# --- Optional ----------------------------------------------------------
headers: # HTTP headers sent on every backend request
- name: Authorization
value:
valueFrom:
secretKeyRef:
name: memory-credentials
key: tokenFields
| Field | Type | Required | Description |
|---|---|---|---|
spec.address | ValueSource | yes | The memory service endpoint. Supply a direct value (URL) or a valueFrom source — serviceRef (in-cluster service), configMapKeyRef, secretKeyRef, or queryParameterRef. The resolved value is written back to status.lastResolvedAddress. |
spec.headers[] | list of Header | no | HTTP headers included in every request to the backend. Each entry has a name and a value that is either a direct value or a valueFrom source (configMapKeyRef, secretKeyRef, queryParameterRef). Use for auth tokens or routing headers. |
Address resolution
spec.address is a ValueSource, so the backend endpoint can be a literal URL or resolved from cluster state:
spec:
address:
valueFrom:
serviceRef:
name: ark-broker # resolves to the in-cluster service address
port: "8080"spec:
address:
value: http://memory.example.com:8080 # direct URLThe controller resolves the address and records the result in status.lastResolvedAddress. Consumers (the completions executor, the Query cleanup finalizer) read the resolved address, falling back to spec.address if the status field is empty.
Using memory from a query
A query selects a Memory via spec.memory.name. If a Memory named default exists in the namespace, it is used automatically for queries that don’t name one. Memory works with all query target types — agents, models, and tools. When a conversationId is provided, messages are persisted to the memory chain regardless of the target type.
Memory in a query to a tool:
apiVersion: ark.mckinsey.com/v1alpha1
kind: Query
metadata:
name: tool-with-memory
spec:
input: "Remember this message for later"
target:
type: tool
name: noop
conversationId: "my-conversation"A follow-up query to an agent on the same conversation ID has access to the tool query’s messages:
apiVersion: ark.mckinsey.com/v1alpha1
kind: Query
metadata:
name: follow-up-agent-query
spec:
input: "What was the last message?"
target:
type: agent
name: my-agent
conversationId: "my-conversation"Memory can also be specified via the CLIs:
# Using fark
fark query --input "Start our conversation" --conversation-id "my-conversation" my-agent
# Using ark
ark query tool/noop "Remember this message for later" --conversation-id "my-conversation"
ark query agent/my-agent "What was the last message?" --conversation-id "my-conversation"Session ID vs Conversation ID:
- sessionId: Groups related queries for tracking and telemetry purposes.
- conversationId: Conversation threading identifier sent to all execution engines as A2A
contextId. The completions engine uses it for memory continuity; named engines can use it for their own session management.
When creating a query in the dashboard it is also possible to specify the memory resource. Note that in the dashboard ‘chat’ window, no memory is used — messages are simply stored client-side as is common for chat applications.
Memory HTTP API
A memory backend is an HTTP server implementing the following endpoints. The ark-broker is the reference implementation; the Ark API gateway proxies the same contract.
| Method | Endpoint | Description |
|---|---|---|
| POST | /conversations | Create a new conversation |
| GET | /conversations | List all conversation IDs |
| GET | /conversations/{id} | Get conversation details and messages |
| DELETE | /conversations/{id} | Delete a specific conversation |
| POST | /messages | Store messages (requires conversation_id) |
| GET | /messages | Retrieve messages with optional filtering |
| DELETE | /queries/{queryId}/messages | Delete all messages for a query across all conversations |
| GET | /health | Health check |
Create Conversation
POST /conversations
Creates a new conversation and returns its ID. This must be called before storing messages.
Response:
{
"conversation_id": "550e8400-e29b-41d4-a716-446655440000"
}Store Messages
POST /messages
Stores messages for an existing conversation. The conversation_id is required.
{
"conversation_id": "550e8400-e29b-41d4-a716-446655440000",
"query_id": "query-uuid",
"ttl_seconds": 2592000,
"messages": [
{
"role": "user",
"content": "What is the weather like?"
},
{
"role": "assistant",
"content": "I don't have access to real-time weather data."
}
]
}ttl_seconds (optional) sets a per-message expiry: each stored message is stamped with an expires_at snapshot and filtered out on read once it elapses. This expires messages on a timer — it does not delete them; see Delete Query Messages for explicit removal. When omitted, the backend applies its default visibility TTL.
Get Conversation
GET /conversations/{id}
Returns conversation details including all messages:
{
"conversation_id": "550e8400-e29b-41d4-a716-446655440000",
"messages": [
{
"timestamp": "2024-01-01T12:00:00Z",
"conversation_id": "550e8400-e29b-41d4-a716-446655440000",
"query_id": "query-uuid",
"message": { "role": "user", "content": "Hello" },
"sequence": 1
}
]
}Retrieve Messages
GET /messages?conversation_id={id}&query_id={id}&limit={n}&offset={n}
Retrieves stored conversation messages:
conversation_id(optional) - Filter by conversationquery_id(optional) - Filter by querylimit(optional, default: 100) - Max messages to returnoffset(optional, default: 0) - Skip messages for pagination
Returns timestamped message records:
{
"messages": [
{
"timestamp": "2024-01-01T12:00:00Z",
"conversation_id": "conv-abc123",
"query_id": "query-uuid",
"message": {
"role": "user",
"content": "What is the weather like?"
}
}
],
"total": 100,
"limit": 50,
"offset": 0
}List Conversations
GET /conversations
Returns object with conversation IDs:
{"conversations": ["conv-1", "conv-2", "conv-3"]}Delete Query Messages
DELETE /queries/{queryId}/messages
Hard-deletes all messages for a query across every conversation. The Ark controller calls this to clean up broker state when a Query is deleted — see Query → Deletion and cleanup. The operation is idempotent.
Response:
{
"status": "success",
"message": "Query <queryId> messages deleted"
}A backend that does not implement this endpoint should return 404 or 405; the controller treats both as “not supported” and skips cleanup.
Status
status:
phase: ready
lastResolvedAddress: http://ark-broker.default.svc.cluster.local:8080
message: Memory backend reachableStatus fields
| Field | Description |
|---|---|
status.phase | One of running, ready, error. |
status.lastResolvedAddress | Last resolved value of spec.address, kept for reference. Consumers read this first, falling back to spec.address. |
status.message | Additional information about the current status (e.g. a resolution or reachability error). |
Print columns
kubectl get memories prints NAME, PHASE (.status.phase), ADDRESS (.status.lastResolvedAddress), and AGE.
Related
- Query — references a
Memoryviaspec.memory. - Broker Service — the reference memory backend and its HTTP API.
- Ark API — gateway that proxies the memory contract.