Everything you need to give your AI agents persistent memory.
pip install sulcus
# With async support:
pip install sulcus[async]Python 3.9+ · Zero dependencies · Async via optional httpx
npm install sulcusNode 18+ · Zero dependencies · Full TypeScript support
from sulcus import Sulcus
client = Sulcus(api_key="sk-...")
# Store memories with full lifecycle control
client.remember("User prefers dark mode", memory_type="preference",
decay_class="slow", # slow decay — preferences persist
is_pinned=True, # pinned — never decays
min_heat=0.3, # floor heat — never goes below 0.3
key_points=["dark mode", "UI preference"])
client.remember("API rate limit is 1000/min", memory_type="semantic")
# Search
results = client.search("dark mode")
for m in results:
print(f"[{m.memory_type}] {m.pointer_summary} (heat: {m.current_heat:.2f}")
# Recall feedback — reinforces good memories, penalizes bad ones
client.feedback(results[0].id, "relevant") # boosts heat + stability
client.feedback(results[1].id, "outdated") # marks as superseded
# List with filters
memories = client.list(page=1, page_size=10, memory_type="preference")
# Pin / unpin
client.pin(memories[0].id)
client.unpin(memories[0].id)
# Bulk operations
client.bulk_update(["id-1", "id-2"], is_pinned=True, heat=0.9)
client.bulk_delete(memory_type="episodic", namespace="old-session")
# Analytics
analytics = client.recall_analytics()
print(analytics["suggestions"]) # tuning recommendations based on feedback patternsimport { Sulcus } from "sulcus";
const client = new Sulcus({ apiKey: "sk-..." });
// Store memories with full lifecycle control
await client.remember("User prefers dark mode", {
memoryType: "preference",
decayClass: "slow", // slow decay — preferences persist
isPinned: true, // pinned — never decays
minHeat: 0.3, // floor heat — never goes below 0.3
keyPoints: ["dark mode", "UI preference"],
});
await client.remember("API rate limit is 1000/min", { memoryType: "semantic" });
// Search
const results = await client.search("dark mode");
for (const m of results) {
console.log(`[${m.memory_type}] ${m.pointer_summary} (heat: ${m.current_heat.toFixed(2)})`);
}
// Recall feedback — reinforces good memories, penalizes bad ones
await client.feedback(results[0].id, "relevant"); // boosts heat + stability
await client.feedback(results[1].id, "outdated"); // marks as superseded
// List with filters
const memories = await client.list({ page: 1, pageSize: 10, memoryType: "preference" });
// Bulk operations
await client.bulkUpdate(["id-1", "id-2"], { isPinned: true, heat: 0.9 });
await client.bulkDelete({ memoryType: "episodic", namespace: "old-session" });
// Analytics & tuning suggestions
const analytics = await client.recallAnalytics();
console.log(analytics.suggestions);import asyncio
from sulcus import AsyncSulcus
async def main():
async with AsyncSulcus(api_key="sk-...") as client:
await client.remember("async memory", memory_type="semantic")
results = await client.search("async")
print(results)
asyncio.run(main())episodicDecay: FastEvents, conversations, time-bound experiences
"Met with design team, decided on blue theme"
semanticDecay: SlowFacts, knowledge, definitions
"Python 3.12 requires typing_extensions >= 4.0"
preferenceDecay: MediumUser preferences, settings, opinions
"User prefers dark mode and monospace fonts"
proceduralDecay: SlowHow-to knowledge, workflows, recipes
"To deploy: git push, then az acr build, then update app"
momentDecay: GlacialPersonality-defining interactions, relationship dynamics
"User laughed and said 'that's why I trust you'"
Every memory has a heat value that decays over time. You control the speed, the floor, and the permanence.
fastHalf-life: ~2 hoursEphemeral context, short-lived tasks
normalHalf-life: ~24 hoursStandard memories (default)
slowHalf-life: ~7 daysImportant facts, preferences
glacialHalf-life: ~30 daysCore identity, relationships, moments
is_pinnedPrevents ALL heat decay. Memory stays hot forever. Use for core identity, rules, permanent preferences.
min_heatFloor value (0.0–1.0). Memory decays but never drops below this. Ensures minimum recall priority.
decay_classOverride the default decay speed for this memory type. Options: fast, normal, slow, glacial.
key_pointsStructured metadata — list of key takeaways. Improves search relevance and context building.
Sulcus speaks MCP (Model Context Protocol) natively. Connect any MCP-compatible client — Claude Desktop, OpenAI agents, custom hosts — directly to your memory graph.
# Claude Desktop — add to claude_desktop_config.json:
{
"mcpServers": {
"sulcus": {
"url": "https://api.sulcus.ca/mcp",
"transport": "streamable-http",
"headers": {
"Authorization": "Bearer sk-your-api-key"
}
}
}
}29 MCP tools available: search_memory, commit_memory, record_memory, build_context, list_hot_nodes, tick, prune_cold_memories, forget_memory, page_in, compact_wal, sync_now, create_trigger, list_triggers, update_trigger, delete_trigger, trigger_history, and more.
Set rules on your memory graph. When events happen — a memory is stored, recalled, boosted, or decays — Sulcus fires actions automatically. No competitor has this. Triggers run server-side and locally, fire during MCP tool calls, and surface notifications inline.
on_storeNew memory created
on_recallMemory searched/recalled
on_boostMemory heat increased
on_relateEdge created between memories
on_decayHeat dropped during tick
on_thresholdHeat crosses boundary
pinboosttagdeprecatenotifywebhookchain (v2)# Reactive Triggers — automate memory lifecycle
from sulcus import Sulcus
client = Sulcus(api_key="sk-...")
# Auto-pin every preference memory
client.create_trigger(
event="on_store",
action="pin",
name="auto-pin-preferences",
filter_memory_type="preference"
)
# Boost memories every time they're recalled (spaced repetition)
client.create_trigger(
event="on_recall",
action="boost",
name="reinforce-on-recall",
action_config={"strength": 0.15}
)
# Webhook when critical memory starts cooling
client.create_trigger(
event="on_threshold",
action="webhook",
name="alert-cold-procedures",
filter_memory_type="procedural",
filter_heat_below=0.3,
action_config={"url": "https://hooks.slack.com/your-webhook"}
)
# List active triggers
triggers = client.list_triggers()
for t in triggers:
print(f"{t['name']}: {t['event']} → {t['action']} (fired {t['fire_count']}x)")import { Sulcus } from "sulcus";
const client = new Sulcus({ apiKey: "sk-..." });
// Auto-pin every preference memory
await client.createTrigger("on_store", "pin", {
name: "auto-pin-preferences",
filterMemoryType: "preference",
});
// Boost memories every time they're recalled
await client.createTrigger("on_recall", "boost", {
name: "reinforce-on-recall",
actionConfig: { strength: 0.15 },
});
// Webhook when critical memory starts cooling
await client.createTrigger("on_threshold", "webhook", {
name: "alert-cold-procedures",
filterMemoryType: "procedural",
filterHeatBelow: 0.3,
actionConfig: { url: "https://hooks.slack.com/your-webhook" },
});
// Check trigger history
const history = await client.triggerHistory();
for (const h of history) {
console.log(`${h.event} → ${h.action} at ${h.fired_at}`);
}# Create a trigger
curl -X POST https://api.sulcus.ca/api/v1/triggers \
-H "Authorization: Bearer sk-..." \
-H "Content-Type: application/json" \
-d '{
"name": "auto-pin-preferences",
"event": "on_store",
"action": "pin",
"filter_memory_type": "preference"
}'
# List triggers
curl https://api.sulcus.ca/api/v1/triggers \
-H "Authorization: Bearer sk-..."
# Delete a trigger
curl -X DELETE https://api.sulcus.ca/api/v1/triggers/{id} \
-H "Authorization: Bearer sk-..."Base URL: https://api.sulcus.ca
Authentication: Authorization: Bearer <api-key>
/api/v1/agent/nodesCreate a memory node/api/v1/agent/nodesList memories (paginated)/api/v1/agent/nodes/:idGet a single memory/api/v1/agent/nodes/:idUpdate a memory/api/v1/agent/nodes/:idDelete a memory/api/v1/agent/searchText search memories/api/v1/agent/hot_nodesList hottest memories/api/v1/agent/nodes/bulk-patchBulk update memories (shared patch or per-node)/api/v1/agent/nodes/bulkBulk delete by IDs, type, or namespace/api/v1/agent/syncCRDT sync (push/pull ops)/api/v1/metricsStorage & health metrics/api/v1/orgTenant/org info & limits/api/v1/keysList API keys/api/v1/keysGenerate new API key/api/v1/settings/thermoGet thermodynamic engine config/api/v1/settings/thermoUpdate thermodynamic engine config/api/v1/feedbackRecall quality feedback (relevant/irrelevant/outdated)/api/v1/analytics/recallRecall analytics with tuning suggestions/api/v1/activityActivity log (paginated, cursor-based)/api/v1/gamification/profileXP, level, badges, streaks/api/v1/triggersList active triggers/api/v1/triggersCreate a reactive trigger/api/v1/triggers/:idUpdate a trigger/api/v1/triggers/:idDelete a trigger/api/v1/triggers/historyTrigger firing history/mcpMCP Streamable HTTP (JSON-RPC)/mcpMCP SSE notification stream# Create a memory
curl -X POST https://api.sulcus.ca/api/v1/agent/nodes \
-H "Authorization: Bearer sk-..." \
-H "Content-Type: application/json" \
-d '{"label": "User prefers dark mode", "memory_type": "preference"}'
# Search memories
curl -X POST https://api.sulcus.ca/api/v1/agent/search \
-H "Authorization: Bearer sk-..." \
-H "Content-Type: application/json" \
-d '{"query": "dark mode", "limit": 10}'
# List memories
curl https://api.sulcus.ca/api/v1/agent/nodes?page=1&page_size=10 \
-H "Authorization: Bearer sk-..."Point any SDK at your own server. The entire stack — server, database, sync — runs on your infrastructure.
# Python
client = Sulcus(api_key="your-key", base_url="http://localhost:4200")
# Node.js
const client = new Sulcus({ apiKey: "your-key", baseUrl: "http://localhost:4200" });Dedicated packages for popular LLM frameworks. Each wraps the Sulcus API with framework-native abstractions.
pip install sulcus-langchainpip install sulcus-llamaindexnpm install sulcus-vercel-aiCopy tools.jsonCopy tools.jsonpip install sulcus-crewaipip install sulcus-deepagentsnpm install -g sulcus-cliopenclaw plugins installMarketplace (coming)