Memory Agent Blocks
Kilroy AI Services includes four built-in wf_agent blocks for persistent memory storage. They provide a
lightweight key/value-style record store backed by a per-pipeline SQLite database, accessible from any
pipeline node that can send a swarm message.
Overview
Memory agents let a pipeline store, retrieve, search, and delete text-based records that persist across
conversations. Each record is written to a named memory store identified by a memory_id path. All four
blocks share a common record schema and a common configuration approach.
Record Schema
Every memory record has the following fields:
| Field | Type | Description |
|---|---|---|
guid |
string | Unique identifier for the record (UUID) |
scope |
string | Logical grouping label |
kind |
string | Record category tag (e.g. "observation", "summary") |
content |
string | The stored text |
source |
string | Provenance label (defaults to the sending agent's name) |
tags |
array | Optional array of string tags |
created_at |
number | Unix timestamp (ms) when the record was created |
updated_at |
number | Unix timestamp (ms) of the last modification |
Memory ID Convention
All four blocks require a memory_id in their wf_webhook_args. This is a relative path within the
platform's memory root directory:
For example: "instances/a3d8f1b2/memories". Using the pipeline UID in the path keeps each pipeline's
data isolated from other pipelines. The memory_id also serves as the default scope value for writes
if no explicit scope is specified.
Pipeline bundle note: In a pipeline bundle, use the substitution token form
"instances/${pipeline_uid}/memories"rather than a literal UID. The token is replaced with the real pipeline UID at deploy time, ensuring each deployed instance has its own isolated memory store. The literal UID form (e.g."instances/a3d8f1b2/memories") is appropriate only when referencing a specific pre-existing store by design.
Dispatch Pattern
A common pipeline design is to intercept slash-commands from a chat agent using regex_match relay agents,
which extract the command argument and re-publish it as the incoming message to the memory block. Because
the memory blocks read args[0].text as their payload, this works without any custom scripting. See each
block below for how args[0].text is used.
memory_write
Description
Stores a text string as a new record in a memory store. Each call creates a new record with a unique guid;
records are not deduplicated or updated — they accumulate.
Use Case
Use memory_write when a pipeline agent should remember a fact, observation, or event for later recall.
Typical patterns:
- A user posts
/write The meeting is at 3pmand aregex_matchrelay extracts the payload and delivers it tomemory_write. - An AI agent produces a response that should be persisted automatically before being sent to the chat.
Input from Swarm
| Field | Required | Description |
|---|---|---|
args[0].text |
Required | The text content to store as the new record |
If args[0].text is empty or missing, the hook returns { "error": "no content" } without writing.
Output to Swarm
A JSON string published back to the output swarm:
| Condition | Response |
|---|---|
| Success | { "guid": "<uuid>" } |
| Failure | { "error": "<message>" } |
wf_webhook_args
These are set on the agent node in the pipeline editor under Webhook Args:
| Key | Type | Required | Default | Description |
|---|---|---|---|---|
memory_id |
string | Required | — | Memory store path, e.g. "instances/a3d8f1b2/memories" |
scope |
string | Optional | memory_id value |
Logical scope label for grouping records |
kind |
string | Optional | "observation" |
Category tag for the record |
source |
string | Optional | Agent name | Provenance label stored with the record |
tags |
string | Optional | "[]" |
JSON array string of tags, e.g. '["important","fact"]' |
Example wf_webhook_args JSON
memory_read
Description
Reads records from a memory store and returns them as a JSON array. Optional filters narrow results by
kind, scope, or source. With no filters, all records are returned.
Use Case
Use memory_read to retrieve stored memories for display, to feed context into an AI agent, or for
auditing pipeline state. Typical patterns:
- A user posts
/readto list all stored memories in a chat interface. - An AI context-loading step fetches all records of
kind: "summary"before calling the LLM.
Input from Swarm
No specific payload is required. The incoming message triggers the read operation. The content of
args[0].text is ignored.
Output to Swarm
A JSON string published back to the output swarm:
| Condition | Response |
|---|---|
| Success (records found) | JSON array of record objects (see Record Schema above) |
| Success (no matches) | [] (empty array) |
| Failure | { "error": "<message>" } |
wf_webhook_args
| Key | Type | Required | Default | Description |
|---|---|---|---|---|
memory_id |
string | Required | — | Memory store path |
kind |
string | Optional | — | If set, returns only records with this kind |
scope |
string | Optional | — | If set, returns only records with this scope |
source |
string | Optional | — | If set, returns only records with this source label |
output_template |
string | Optional | — | When set, switches output to text/plain. Applied to each record; results are concatenated. Tokens: {{content}}, {{kind}}, {{scope}}, {{guid}}, {{source}}, {{tags}}. Omit to receive the default JSON array. |
Example wf_webhook_args JSON
To filter to summaries only:
To load all soul records as plain text directly into set_prompts:
{
"memory_id": "instances/a3d8f1b2/cognitive",
"kind": "soul",
"output_template": "{{content}}\n\n"
}
Description
Full-text search across a memory store using SQLite FTS5. The incoming message text is used as the
search query. Results are returned ranked by relevance. Optional kind and scope filters are applied
after the FTS match.
Use Case
Use memory_search when you want to retrieve memory records that are semantically relevant to a user's
query or a pipeline topic. Typical patterns:
- A user posts
/search blue skyand aregex_matchrelay extracts"blue sky"and delivers it as the message text tomemory_search. - A RAG (Retrieval-Augmented Generation) step searches stored knowledge before composing an LLM prompt.
Input from Swarm
| Field | Required | Description |
|---|---|---|
args[0].text |
Required | The search query string |
If args[0].text is empty or missing, the hook returns an empty array without querying.
Output to Swarm
A JSON string published back to the output swarm:
| Condition | Response |
|---|---|
| Success (matches found) | JSON array of record objects ordered by FTS5 relevance |
| Success (no matches) | [] (empty array) |
| Failure | { "error": "<message>" } |
When output_template is set, a plain-text string is published instead (see below).
wf_webhook_args
| Key | Type | Required | Default | Description |
|---|---|---|---|---|
memory_id |
string | Required | — | Memory store path |
kind |
string | Optional | — | Post-FTS filter: include only records of this kind |
scope |
string | Optional | — | Post-FTS filter: include only records with this scope |
tags |
string | Optional | — | Post-FTS filter: JSON array string or comma-separated tag list; all tags must be present (AND semantics). e.g. '["important","fact"]' or "important,fact" |
output_template |
string | Optional | — | When set, switches output to text/plain. Applied to each result record in relevance order; results are concatenated. Tokens: {{content}}, {{kind}}, {{scope}}, {{guid}}, {{source}}, {{tags}}. Omit to receive the default JSON array. |
Example wf_webhook_args JSON
memory_delete
Description
Deletes memory records by guid (single record) or kind (bulk deletion). If both are set, guid takes
priority. If neither guid nor kind can be resolved, the hook returns without deleting anything.
The guid may be supplied in two ways:
- Static — set
guidinwf_webhook_argsdirectly (useful when the guid is known at design time). - Dynamic (dispatch pattern) — leave
guidempty inwf_webhook_args; the hook falls back toargs[0].textas the guid. This allows aregex_matchrelay to extract the guid from a/delete <guid>command and deliver it as the message payload.
Use Case
Use memory_delete to remove a specific record by guid or to bulk-clear all records of a given kind.
Typical patterns:
- A user posts
/delete 3f8a2c1d-...and aregex_matchrelay extracts the guid and delivers it tomemory_deleteas the message text. - An administrative pipeline step clears all records of
kind: "temp"at the end of a session.
Input from Swarm
| Field | Condition | Description |
|---|---|---|
args[0].text |
Used when targs.guid is empty |
Treated as the guid to delete |
If neither targs.guid, args[0].text, nor targs.kind resolves to a non-empty value, the hook
returns { "skipped": "no query" } without deleting anything.
Output to Swarm
A JSON string published back to the output swarm:
| Condition | Response |
|---|---|
| Success | { "deleted": <count> } where count ≥ 0 |
| No guid or kind provided | { "skipped": "no query" } |
| Failure | { "error": "<message>" } |
wf_webhook_args
| Key | Type | Required | Default | Description |
|---|---|---|---|---|
memory_id |
string | Required | — | Memory store path |
guid |
string | Optional | — | Delete the specific record with this guid. If empty, falls back to args[0].text |
kind |
string | Optional | — | Bulk-delete all records with this kind (used only when guid is not resolved) |
Example wf_webhook_args JSON
To delete by guid (static, guid known at design time):
To delete by kind:
To use the dispatch pattern (user provides guid at runtime via /delete <guid>):
memory_append
Description
Appends text to an existing memory record's content, identified by guid or by scope+kind+source
lookup. If no matching record is found, a new record is created (same behavior as memory_write).
Always returns a { guid } so the caller can retain it for future append calls.
Use Case
Use memory_append to accumulate growing content into a single record without a read–modify–write cycle.
Typical patterns:
- A session observation sink appends each AI response to a running
kind: "session_log"record. - A pipeline step that receives a guid from a prior
memory_writecall appends follow-up content to the same record by passing the guid downstream.
Lookup Priority
- If
targs.guidis non-empty — append to that specific record. If the guid is not found, fall through. - If
targs.scopeand/ortargs.kindare set — find the first matching record and append to it. If no match, create a new record. - If neither guid nor scope/kind are set — create a new record (same as
memory_write).
Input from Swarm
| Field | Required | Description |
|---|---|---|
args[0].text |
Required | Text to append (or full content for a new record) |
Output to Swarm
A JSON string published back to the output swarm:
| Condition | Response |
|---|---|
| Appended to existing record | { "guid": "<uuid>", "appended": true } |
| Created new record | { "guid": "<uuid>", "created": true } |
No content in args[0].text |
{ "error": "no content" } |
| Failure | { "error": "<message>" } |
wf_webhook_args
| Key | Type | Required | Default | Description |
|---|---|---|---|---|
memory_id |
string | Required | — | Memory store path |
guid |
string | Optional | — | GUID of the record to append to. If empty or not found, falls back to scope+kind lookup. |
scope |
string | Optional | memory_id value |
Scope for lookup fallback and new-record creation |
kind |
string | Optional | "observation" |
Kind for lookup fallback and new-record creation |
source |
string | Optional | Agent name | Source label for lookup fallback and new-record creation |
tags |
string | Optional | "[]" |
JSON array string of tags for new-record creation only |
Example wf_webhook_args JSON
Append to a record identified by guid (guid may be populated dynamically):
Append to or create a session_log record by scope+kind lookup:
{
"memory_id": "instances/a3d8f1b2/cognitive",
"scope": "instances/a3d8f1b2/cognitive",
"kind": "session_log"
}
A complete slash-command memory pipeline wires the four memory blocks together with regex_match dispatch
agents. The general pattern is:
- A
chat_agent(User) receives all input. - A
regex_filterwithinvert: trueon^/passes non-command text to an AI agent. - Four
regex_matchagents match/write,/read,/search, and/deleterespectively, extract any argument via a capture group, and re-publish under a uniquewf_agent_name(e.g."dispatch_write"). - Each memory block sets
agent_fromto the matching dispatch agent name so it only fires for its specific command. - Each memory block sets
wf_publish_results: trueand publishes its JSON response to the chat swarm so the result is visible in the conversation.
See the memory_test pipeline in kilroy.sandbox/pipeline editor source docs/memory_test-1.json
for a working editor source document that implements this pattern.
Note
All four memory blocks require wf_publish_results to be enabled on the agent node if you
want the result returned to the swarm. Without it, the webhook runs but no output message
is published.