Memory Curation Cycle
Sulcus runs an autonomous background curator that continuously cleans, consolidates, and optimizes your agent's memory. It reclassifies stale nodes, merges near-duplicates via LLM, summarizes verbose entries, re-vectorizes content, marks stale confidence, and syncs everything to the knowledge graph. Zero human maintenance required. The curator never deletes — it archives.
Sulcus Server v2.9 · Curator Reference · 2026
Overview
Autonomous memory hygiene that runs in the background
Agent memory accumulates fast. Without maintenance, you get duplicates, stale entries, unclassified nodes, and bloated summaries that waste tokens and degrade recall quality. Most memory systems leave this mess for humans to clean up.
Sulcus solves this with the Curator — a background process that runs a multi-step curation cycle over every active namespace. It uses LLM-powered consolidation (via GPT-5.4-nano) to intelligently merge near-duplicates and condense verbose entries, while generating training signals that feed back into the SIU pipeline for continuous model improvement.
Deploy Sulcus. Your memory gets cleaner every 30 minutes. No cron jobs, no manual intervention, no data loss.
Default cycle interval
Cosine similarity merge threshold
Archive-only policy
LLM consolidation engine
When It Runs
Two triggers: timed interval and namespace idle detection
The curator spawns as a background Tokio task when the server starts. After a 15-second stagger delay (to avoid thundering herd with other startup tasks), it enters an interval loop.
Every CURATOR_INTERVAL_SECS seconds (default: 1800 = 30 minutes), the curator runs a full pass over all tenants and their active namespaces.
Namespaces with no interactions for 10 minutes (IDLE_THRESHOLD_SECS = 600) are prioritized for curation. The curator cleans up while your agents are quiet.
// From crates/sulcus-server/src/curator.rs
pub fn spawn(pool: PgPool) {
let interval_secs = std::env::var("CURATOR_INTERVAL_SECS")
.ok()
.and_then(|s| s.parse::<u64>().ok())
.unwrap_or(1_800); // 30 minutes default
tokio::spawn(async move {
// Stagger startup: 15s delay avoids thundering herd
tokio::time::sleep(Duration::from_secs(15)).await;
let mut interval = tokio::time::interval(
Duration::from_secs(interval_secs)
);
interval.tick().await; // skip immediate tick
loop {
interval.tick().await;
run_curation_cycle(&pool, extraction_config).await;
}
});
}The 7 Steps
Each curation cycle runs these steps per tenant/namespace
The curator iterates over every active tenant, then every namespace within that tenant. For each namespace, it executes seven steps in order. Each step is independent and fault-tolerant — if one fails, the rest still run.
async fn curate_namespace(pool, tenant_id, namespace, config) {
// Step 1: Re-classify stale unrecalled nodes
stats.reclassified += step_reclassify(pool, tenant_id, namespace);
// Step 2: Consolidate near-duplicates (archive, never delete)
stats.consolidated += step_consolidate_duplicates(
pool, tenant_id, namespace, config
);
// Step 3: Summarize verbose nodes
stats.summarized += step_summarize_verbose(
pool, tenant_id, namespace, config
);
// Step 4: Re-vectorize nodes with missing embeddings
stats.revectorized += step_revectorize(pool, tenant_id, namespace);
// Step 5: Mark stale confidence (not recalled in 30+ days)
stats.stale_marked += step_mark_stale_confidence(
pool, tenant_id, namespace
);
// Step 6: Sync modified nodes to AGE graph
step_sync_age_graph(pool, tenant_id, namespace);
// Step 7: Log curation activity
log_curation_activity(pool, tenant_id, namespace, &stats);
}Step 1: Re-classify Stale Nodes
Ensure old unrecalled memories get properly typed
Nodes with recall_count = 0 that are lagging more than 100 epochsbehind the namespace's current interaction epoch get flagged for SIU reclassification.
These are memories that were stored but never recalled — they may have been classified hastily during a high-activity period or stored before the SIU models were fully trained. The curator generates a reclassify_pending signal in the training_signals table, which feeds back into offline model retraining.
Selection Criteria
recall_count = 0Never been recalled by any queryepoch_lag > 100Far behind the namespace epocharchived_at IS NULLOnly active (non-archived) nodesProcesses up to 50 nodes per namespace per cycle to avoid overloading the training signal table.
Step 2: Consolidate Near-Duplicates
LLM-powered merge with archive-only policy
The curator finds pairs of nodes within the same namespace and memory type whose vector embeddings have cosine similarity greater than 0.92. These are near-duplicates — different phrasings of the same knowledge.
When a pair is found, the curator keeps the higher-utility nodeand archives the other. The archived node's content is merged into the kept node via LLM-powered consolidation:
// LLM consolidation via GPT-5.4-nano
let prompt = "Given two overlapping memories, produce a
single condensed summary that:
- Preserves ALL unique facts, dates, names, technical details
- Removes duplicate information
- Strips [merged: ...] artifacts from prior consolidations
- Keeps the result concise but complete
- Maximum 2000 characters";
// Uses Azure Foundry Responses API with JSON Schema
let response = client
.post(&config.endpoint)
.header("api-key", &config.api_key)
.json(&CuratorApiRequest {
model: config.model, // GPT-5.4-nano
text: { format: json_schema }, // structured output
})
.send().await;The LLM produces a single condensed summary that preserves all unique facts from both memories while removing duplicates. If the LLM is unavailable (extraction not configured), the curator falls back to SQL-append behavior: pointer_summary || '\n[merged: ...]'.
After LLM consolidation, the kept node's embedding is nullified (set to NULL) so it gets re-vectorized in Step 4 with the new consolidated content.
With LLM (GPT-5.4-nano)
- ✓ Clean, deduplicated summary
- ✓ Strips [merged: ...] nesting artifacts
- ✓ Preserves all unique facts
- ✓ Max 2000 characters
- ✓ Embedding nullified → re-vectorized
Fallback (No LLM)
- • SQL append: [merged: content]
- • Can accumulate nesting over cycles
- • No deduplication of overlapping facts
- • Embedding preserved (no re-vectorization)
Processes up to 20 duplicate pairs per namespace per cycle. Archived nodes sync to the AGE graph immediately.
Step 3: Summarize Verbose Nodes
Condense long entries to save tokens without losing meaning
Nodes with pointer_summary longer than 500 characters are candidates for summarization if they meet either condition:
- low recall
recall_count < 3— rarely accessed, safe to condense - merged artifacts Contains
[merged: ...]nesting from prior non-LLM consolidations
With LLM enabled, GPT-5.4-nano condenses the node to a concise version (max 500 characters) preserving all key facts. Without LLM, the curator truncates to 200 characters at a word boundary with an ellipsis.
Processes up to 30 verbose nodes per namespace per cycle.
Step 4: Re-vectorize
Generate fresh embeddings for nodes missing vectors
Nodes that had their embeddings nullified during consolidation or summarization — or nodes that were stored before the embedding model was configured — need fresh vector embeddings. The curator identifies these nodes and flags them for the backfill task.
Actual embedding generation happens via the server's backfill pipeline on the next restart or batch cycle. The curator's role here is observability: logging how many nodes are waiting for vectors so operators can track embedding pipeline health.
When a node's content changes (via LLM consolidation or summarization), the old embedding no longer represents the new text. Setting it to NULL ensures the node gets a fresh, accurate vector on the next backfill pass. This prevents stale embeddings from degrading semantic search quality.
Step 5: Mark Stale Confidence
Nodes not recalled in 30+ days get flagged
If a memory hasn't been recalled in 30 days, its confidence field is set to stale. This signals to downstream consumers that the information may be outdated.
Nodes with confidence verified (explicitly confirmed by an agent or user) or already staleare excluded. Stale nodes are still recalled normally — consumers can filter or deprioritize them based on the confidence field.
Confidence Levels
Step 6: AGE Graph Sync
Keep the knowledge graph in sync with curation changes
Every node modified in the current curation window (last 35 minutes) gets synced to the Apache AGE knowledge graph. This includes nodes that were consolidated, summarized, or archived.
Archived nodes are properly marked in the graph via archive_memory_vertex, ensuring the knowledge graph always reflects the current state of memory. Active nodes get their vertices updated with fresh heat values, pin status, and summary text.
Updated via ensure_memory_vertex— syncs heat, pin status, summary, and namespace to the graph vertex.
Marked via archive_memory_vertex— the graph vertex is flagged as archived, not deleted. Relationships preserved.
Syncs up to 100 modified nodes per namespace per cycle. The 35-minute window slightly overlaps the 30-minute cycle to avoid missing nodes modified near cycle boundaries.
Step 7: Activity Logging
Full observability into every curation cycle
After each namespace pass, the curator logs a structured activity record with counts for every step. This feeds the dashboard and enables operators to monitor curation health across all tenants.
-- Query curation activity for a tenant
SELECT
metadata->>'namespace' AS namespace,
metadata->>'reclassified' AS reclassified,
metadata->>'consolidated' AS consolidated,
metadata->>'summarized' AS summarized,
metadata->>'revectorized' AS revectorized,
metadata->>'stale_marked' AS stale_marked,
created_at
FROM activity_log
WHERE tenant_id = 'your-tenant-id'
AND action = 'curation_cycle'
ORDER BY created_at DESC
LIMIT 10;| Metric | Description |
|---|---|
| reclassified | Nodes flagged for SIU reclassification |
| consolidated | Duplicate pairs merged (weaker node archived) |
| summarized | Verbose nodes condensed via LLM or truncation |
| revectorized | Nodes identified as needing fresh embeddings |
| stale_marked | Nodes marked with stale confidence |
Never Deletes Policy
Archive-only — your data is always recoverable
The curator never deletes a memory node. When a near-duplicate is found, the weaker node is archived by setting its archived_at timestamp. The original content, metadata, and relationships are fully preserved.
This is a core design principle with direct enterprise implications:
Every memory that ever existed is recoverable. Compliance teams can trace the full history of any knowledge node.
Consolidation merges content into the surviving node before archiving. Information is preserved, never destroyed.
Archived nodes can be restored by clearing archived_at. The curator's work is always undoable.
Training Signal Generation
How the curation cycle feeds back into offline model training
The curation cycle doesn't just clean memory — it generates training signals that improve the SIU models over time. This creates a virtuous feedback loop: curation identifies problems, signals record them, and retraining makes the models better at preventing those problems in the first place.
When the curator flags a stale unrecalled node for reclassification (Step 1), it inserts a reclassify_pending signal into the training_signals table with source = 'curator'. These signals accumulate alongside agent-generated signals (from store, delete, pin, boost) and feed into the same retraining pipeline.
-- Reclassify signals generated by the curator
INSERT INTO training_signals
(memory_id, tenant_id, signal_type,
corrected_type, content_snapshot, source)
VALUES ($1, $2, 'reclassify_pending', $3, $4, 'curator')
ON CONFLICT DO NOTHING;
-- These signals feed back into SIU model retraining:
-- SIVU learns better base_utility scoring
-- SICU learns better memory type classificationThe Feedback Loop
Curator detects problems
Stale nodes, duplicates, verbose entries, missing vectors
Signals record corrections
reclassify_pending signals capture what went wrong and why
Retraining improves models
SIVU learns better utility scoring, SICU learns better type classification
Fewer problems next cycle
Better models mean fewer misclassifications, fewer duplicates, less noise
See the Training Signals documentation for the full retraining pipeline, signal table schema, and SDK integration.
Configuration
Environment variables that control curation behavior
The curator is configured entirely through environment variables. No code changes required — adjust the interval, enable LLM consolidation, or point to a different model endpoint.
# Curation interval (default: 1800s = 30 min)
CURATOR_INTERVAL_SECS=1800
# Enable LLM-powered consolidation and summarization
SULCUS_EXTRACTION_ENABLED=true
# Azure Foundry endpoint for GPT-5.4-nano
SULCUS_EXTRACTION_ENDPOINT=https://your-foundry.openai.azure.com/...
SULCUS_EXTRACTION_API_KEY=your-api-key
SULCUS_EXTRACTION_MODEL=gpt-5.4-nano| Variable | Default | Description |
|---|---|---|
| CURATOR_INTERVAL_SECS | 1800 | 30-minute default cycle interval |
| SULCUS_EXTRACTION_ENABLED | false | Enable LLM-powered consolidation and summarization |
| SULCUS_EXTRACTION_ENDPOINT | — | Azure Foundry Responses API endpoint |
| SULCUS_EXTRACTION_API_KEY | — | API key for the extraction endpoint |
| SULCUS_EXTRACTION_MODEL | — | Model name (e.g., gpt-5.4-nano) |
With LLM Enabled
- ✓ Intelligent deduplication via GPT-5.4-nano
- ✓ Clean summaries that strip merge artifacts
- ✓ Embeddings re-generated for consolidated content
- ✓ Higher-quality memory over time
Without LLM
- • SQL-append consolidation (functional but verbose)
- • Truncation-based summarization
- • Zero external API calls — fully offline
- • Still handles all other curation steps normally
Internal Constants
Hardcoded thresholds in the curator engine
| Constant | Value | Purpose |
|---|---|---|
| DEFAULT_INTERVAL_SECS | 1,800 | 30-minute cycle interval |
| IDLE_THRESHOLD_SECS | 600 | 10-minute idle detection window |
| RECLASSIFY_EPOCH_LAG | 100 | Epochs behind before reclassify flag |
| DUPLICATE_SIMILARITY_THRESHOLD | 0.92 | Cosine similarity merge threshold |
| VERBOSE_SUMMARY_CHARS | 500 | Character threshold for summarization |
| VERBOSE_RECALL_MAX | 3 | Max recall count for summarization eligibility |
Memory that maintains itself.
The curation cycle runs automatically on every Sulcus server deployment. Enable LLM-powered consolidation with one environment variable and your agent's memory quality improves continuously — zero human intervention required.