Memory Rules
A memory store's ingestion policy: which completed agent turns feed it, and who decides what is worth keeping.
Overview
A memory rule lives on the destination store, because the question it answers — what feeds this corpus? — is a property of the store, not of any one agent. One store can carry several rules, one agent can feed two stores under different rules, and "what feeds this store?" is one listing rather than a sweep over every agent in the project.
That makes it the opposite half of the write_memory tool an agent gets from
knowledge_config.write_memory_store_id:
write_memory tool | memory rule | |
|---|---|---|
| Who decides | the agent, mid-turn, at its discretion | the platform, after every completed turn |
| What it is | a capability grant | an ingestion policy |
| Reads | the agent's whole context | exactly one turn's transcript |
| Lives on | the agent | the memory store |
Give an agent long-term memory uses a rule; Limit what an agent may do uses the tool.
This module is a verbatim mirror of the runtime: every field, method, status code and error shape is the runtime's own, re-rooted under the project in the path.
See the OpenAPI spec for the full endpoint and schema reference, or browse it rendered under API Reference → Memory Rules.
Data Model
MemoryRule
| Field | Type | Description |
|---|---|---|
id | string | Public memory rule ID (mrule_ prefix). |
memory_store_id | string | The destination store, and the rule's owning scope. Deleting the store deletes its rules. |
project_id | string | The store's project. |
on | string | The event the rule reads — see Which turns a rule reads. |
source_agent_ids | array of string, nullable | Agents whose turns it reads. null is every agent in the project. |
agent_id | string, nullable | Handler agent. Mutually exclusive with tool_id. |
tool_id | string, nullable | Handler tool. Mutually exclusive with agent_id. |
action | string, nullable | Operation id, for a tool handler. |
preset_parameters | object, nullable | Merged into a tool handler's input. The turn's own fields are reserved and win. |
prompt | string, nullable | Replaces the built-in extractor's task instructions. |
ai_provider_id | string, nullable | Provider override for the built-in extractor. |
model | string, nullable | Model override for the built-in extractor. |
enabled | boolean | A disabled rule is kept and never fires. |
created_at | string (date-time) | |
updated_at | string (date-time) |
Key Concepts
Which turns a rule reads
on | Fires | Fit |
|---|---|---|
agents.generation.completed | once per completed turn — conversation or bare, streaming or not | turn-level extraction, and the only event the built-in extractor may bind to |
conversations.message.generated | once per persisted assistant reply | conversation-backed only; for a custom handler |
source_agent_ids narrows further. Leave it out and every agent in the project
feeds the store.
Give an agent long-term memory
binds one agent on agents.generation.completed.
Handlers propose, the store decides
A handler reads one turn's transcript and answers with candidate facts:
{ "facts": [{ "content": "Customer prefers email", "tags": { "kind": "preference" } }] }
| Handler | Set | Behaviour |
|---|---|---|
| built-in extractor | neither agent_id nor tool_id | a tool-less completion over the transcript, asking for atomic facts. prompt, ai_provider_id and model tune it |
| agent | agent_id | the agent is generated against the transcript and its reply is parsed as the contract above |
| tool | tool_id (+ action) | the tool is called with the turn's context plus preset_parameters, and its output is parsed as the contract above |
The three built-in extractor fields cannot be combined with a handler: a handler makes its own model call, or none, so they would be accepted and ignored.
Every candidate then goes through the store's own
write algorithm on its effective
thresholds, and each write appends an
assertion with mechanism: "rule". A
handler can propose garbage and cannot corrupt the store. A rule never blocks or
fails the turn it reads: a handler that throws, times out or answers with
nonsense contributes nothing.
Firings on agents.generation.completed also record a summary on the
originating generation's extraction field, keyed by rule
id — a store can carry several rules, so one flat pair of counts could not say
which produced them.
Give an agent long-term memory
reads both: the fact the built-in extractor stored and the turn's extraction.
Who may do what
Every route needs any project member. Every route is authorized against the store the rule belongs to.
Examples
Add a rule to a store
- CLI
- SDK
- curl
naturali create-memory-rule \
--project-id proj_V1StGXR8Z5jdHi6B \
--memory-store-id mstore_V1StGXR8Z5jdHi6B \
--on agents.generation.completed \
--source-agent-ids agent_V1StGXR8Z5jdHi6B
const { data: memoryRule } = await naturali.memoryRules.createMemoryRule({
path: { project_id: 'proj_V1StGXR8Z5jdHi6B' },
body: {
memory_store_id: 'mstore_V1StGXR8Z5jdHi6B',
on: 'agents.generation.completed',
source_agent_ids: ['agent_V1StGXR8Z5jdHi6B'],
},
});
curl -X POST https://api.naturali.ai/v1/projects/proj_V1StGXR8Z5jdHi6B/memory-rules \
-H "Authorization: Bearer $NATURALI_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"memory_store_id": "mstore_V1StGXR8Z5jdHi6B",
"on": "agents.generation.completed",
"source_agent_ids": ["agent_V1StGXR8Z5jdHi6B"]
}'
Read one store's policy
- CLI
- SDK
- curl
naturali list-memory-rules \
--project-id proj_V1StGXR8Z5jdHi6B \
--memory-store-id mstore_V1StGXR8Z5jdHi6B
const { data: memoryRules } = await naturali.memoryRules.listMemoryRules({
path: { project_id: 'proj_V1StGXR8Z5jdHi6B' },
query: { memory_store_id: 'mstore_V1StGXR8Z5jdHi6B' },
});
curl "https://api.naturali.ai/v1/projects/proj_V1StGXR8Z5jdHi6B/memory-rules?memory_store_id=mstore_V1StGXR8Z5jdHi6B" \
-H "Authorization: Bearer $NATURALI_TOKEN"
Turn a rule off without losing it
- CLI
- SDK
- curl
naturali update-memory-rule \
--project-id proj_V1StGXR8Z5jdHi6B \
--memory-rule-id mrule_V1StGXR8Z5jdHi6B \
--enabled false
const { data: memoryRule } = await naturali.memoryRules.updateMemoryRule({
path: {
project_id: 'proj_V1StGXR8Z5jdHi6B',
memory_rule_id: 'mrule_V1StGXR8Z5jdHi6B',
},
body: { enabled: false },
});
curl -X PATCH https://api.naturali.ai/v1/projects/proj_V1StGXR8Z5jdHi6B/memory-rules/mrule_V1StGXR8Z5jdHi6B \
-H "Authorization: Bearer $NATURALI_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "enabled": false }'