Memory & Threading¶
Spectra provides two persistence mechanisms: memory for cross-session knowledge and threading for conversation management.
Long-Term Memory¶
Memory allows agents and workflows to store and recall information across runs. Unlike workflow state (which lives within a single execution), memory persists indefinitely.
IMemoryStore¶
public interface IMemoryStore
{
Task SetAsync(string ns, string key, MemoryEntry entry, CancellationToken ct = default);
Task<MemoryEntry?> GetAsync(string ns, string key, CancellationToken ct = default);
Task<IReadOnlyList<MemorySearchResult>> SearchAsync(MemorySearchQuery query, CancellationToken ct = default);
Task<IReadOnlyList<MemoryEntry>> ListAsync(string ns, CancellationToken ct = default);
Task DeleteAsync(string ns, string key, CancellationToken ct = default);
MemoryStoreCapabilities Capabilities { get; }
}
Memory Entries¶
Each memory entry has a namespace, key, content, and optional tags and metadata:
var entry = new MemoryEntry
{
Namespace = "user-preferences",
Key = "language",
Content = "User prefers French for all communications",
Tags = new List<string> { "preferences", "language" },
Metadata = new Dictionary<string, string>
{
["source"] = "conversation-123",
["nodeId"] = "onboarding"
},
CreatedAt = DateTimeOffset.UtcNow,
UpdatedAt = DateTimeOffset.UtcNow
};
await memoryStore.SetAsync("user-preferences", "language", entry);
Searching Memory¶
var results = await memoryStore.SearchAsync(new MemorySearchQuery
{
Namespace = "user-preferences",
Text = "language preference",
Tags = new List<string> { "preferences" },
MaxResults = 5
});
foreach (var result in results)
{
Console.WriteLine($"{result.Entry.Key}: {result.Entry.Content}");
}
Built-in Stores¶
InMemoryMemoryStore — For testing and prototyping:
FileMemoryStore — JSON files on disk:
For production, implement IMemoryStore backed by a vector database (Weaviate, Qdrant, Pinecone) for semantic search. See the Build Your Own Memory Store guide.
Memory Tools¶
Agents can interact with memory during their tool-calling loop through two built-in tools:
| Tool | Description |
|---|---|
store_memory |
Save information to memory during an agent loop. |
recall_memory |
Query memory for relevant past information. |
builder.AddAgent("assistant", agent => agent
.WithProvider("openai")
.WithTools("store_memory", "recall_memory")
.WithSystemPrompt("You can remember information across conversations."));
Auto-Injection¶
When MemoryOptions.AutoInjectAgentTools is true, memory tools are automatically added to every agent that has a DelegateToAgentTool configured — you don't need to list them manually.
builder.AddMemory(options =>
{
options.AutoInjectAgentTools = true;
options.DefaultNamespace = MemoryNamespace.From("global");
});
Memory Steps¶
For workflow-level memory operations outside agent loops, Spectra provides two dedicated step types. Use these when you want explicit, deterministic memory operations as nodes in your workflow graph.
MemoryStoreStep¶
Persists data to long-term memory.
StepType: "memory.store"
Inputs¶
| Input | Type | Default | Description |
|---|---|---|---|
namespace |
string |
"global" |
Memory scope. Namespaces isolate entries from each other. |
key |
string |
required | Unique identifier for the entry within the namespace. |
content |
string |
required | The data to store. Non-string values are serialized. |
tags |
string |
— | Comma-separated tags for filtering (e.g. "preferences,user"). |
Outputs¶
| Output | Type | Description |
|---|---|---|
stored |
bool |
true if the operation succeeded. |
key |
string |
The key that was stored. |
action |
string |
"created" for new entries, "updated" for existing ones. |
Example¶
var workflow = Spectra.Workflow("save-preference")
.AddStep("store", new MemoryStoreStep(), inputs: new
{
@namespace = "user-preferences",
key = "theme",
content = "{{inputs.selectedTheme}}",
tags = "preferences,ui"
})
.Build();
Behavior¶
- If an entry with the same namespace and key already exists, it is updated (the
CreatedAttimestamp is preserved). - Metadata is automatically populated with
source = "step",nodeId,runId, andworkflowId. - If no
IMemoryStoreis configured, the step fails with a clear error message.
MemoryRecallStep¶
Retrieves data from long-term memory. Supports three retrieval modes: exact key lookup, text search, and listing.
StepType: "memory.recall"
Inputs¶
| Input | Type | Default | Description |
|---|---|---|---|
namespace |
string |
"global" |
Memory scope to search within. |
key |
string |
— | Exact key lookup. Takes precedence over query. |
query |
string |
— | Search text for finding relevant memories. |
tags |
string |
— | Comma-separated tag filter (only with query). |
maxResults |
int |
10 |
Maximum entries to return. |
Outputs¶
| Output | Type | Description |
|---|---|---|
memories |
List<MemoryEntry> |
The recalled memory entries. |
count |
int |
Number of entries returned. |
found |
bool |
true if at least one entry was found. |
Retrieval Modes¶
The step picks a mode based on which inputs are set:
| Mode | Trigger | Behavior |
|---|---|---|
| Key lookup | key is set |
Returns the single entry with that exact key (or empty). |
| Search | query is set (no key) |
Searches by text, optionally filtered by tags. Uses IMemoryStore.SearchAsync. |
| List | Neither key nor query |
Lists recent entries in the namespace (up to maxResults). |
Example — RAG with Memory¶
var workflow = Spectra.Workflow("remember-and-answer")
.AddStep("recall", new MemoryRecallStep(), inputs: new
{
query = "{{inputs.question}}",
@namespace = "knowledge-base",
maxResults = 5
})
.AddPromptStep("answer", agent: "openai", inputs: new
{
userPrompt = """
Context from memory:
{{nodes.recall.output.memories}}
Question: {{inputs.question}}
Answer based on the context above.
"""
})
.Edge("recall", "answer")
.Build();
Memory Steps vs Memory Tools
Memory Steps (MemoryStoreStep, MemoryRecallStep) are workflow nodes — deterministic, explicit, part of the graph. Use them in DAG-style workflows where you control exactly when memory is read or written.
Memory Tools (store_memory, recall_memory) are called by the LLM during an agent loop. The agent decides when and what to store or recall. Use them when you want the agent to manage its own memory autonomously.
Threading (Conversation Management)¶
Threads track conversation history across multiple interactions. This is how SessionStep maintains context between user turns.
IThreadManager¶
public interface IThreadManager
{
Task<Thread> CreateAsync(string? workflowName = null, CancellationToken ct = default);
Task<Thread?> GetAsync(string threadId, CancellationToken ct = default);
Task UpdateAsync(Thread thread, CancellationToken ct = default);
Task<IReadOnlyList<Thread>> ListAsync(ThreadFilter? filter = null, CancellationToken ct = default);
Task DeleteAsync(string threadId, CancellationToken ct = default);
}
Thread Structure¶
A thread holds the message history and metadata:
public class Thread
{
public string Id { get; }
public List<Message> Messages { get; }
public Dictionary<string, object> Metadata { get; }
public DateTime CreatedAt { get; }
public DateTime UpdatedAt { get; }
}
Retention Policies¶
Control how threads are managed over time:
builder.AddThreadManager<InMemoryThreadManager>(new RetentionPolicy
{
MaxMessages = 100,
MaxAge = TimeSpan.FromDays(30)
});
Built-in Implementations¶
InMemoryThreadManager — For testing and short-lived applications.
For production, implement IThreadManager backed by a database. See the Build Your Own Thread Manager guide.