nocturnusai-memory — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited nocturnusai-memory (Agent Skill) and scored it 45/100 (orange). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 1 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 1 flagged
A base64 string of 128+ characters appears in a documentation file. Encoded prompt injection hides the hostile instruction in base64 — invisible to keyword filters — and relies on the agent's ability to decode it at runtime. There is no normal authoring reason to embed a multi-hundred-byte base64 blob in skill docs.
*.sig, SIGNATURES) outside the documentation.Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Memory lifecycle management for AI agents: salience-ranked retrieval, temporal queries, consolidation, decay, and event streaming. This layer sits on top of the core knowledge base (see nocturnusai-knowledge skill for basic CRUD).
Prerequisites: Server must be running and tenant created. See the nocturnusai-connect skill for setup.
Every fact has a composite salience score computed from three factors:
| Factor | Description | Effect |
|---|---|---|
| Recency | Time since the fact was last accessed or asserted | Recent facts score higher |
| Frequency | How often the fact has been queried or referenced | Frequently used facts score higher |
| Priority | Explicit priority value set via /memory/priority | Manually boosted facts score higher |
The composite score (0.0-1.0) determines which facts are most relevant for an agent's context window. Facts with low salience are candidates for eviction during decay.
Every fact carries temporal metadata:
| Field | Type | Purpose |
|---|---|---|
createdAt | epoch ms | When the fact was first asserted (set automatically) |
validFrom | epoch ms | Earliest time the fact is considered valid (optional) |
validUntil | epoch ms | Latest time the fact is considered valid (optional) |
ttl | milliseconds | Time-to-live duration from assertion time (optional) |
Temporal fields enable point-in-time queries ("What was true yesterday?") and automatic expiration of short-lived facts.
Assert facts (tell) ──> Facts accumulate with temporal metadata
│
▼
Query context (context) ──> Salience-ranked facts for LLM prompt
│
▼
Recall past state (recall) ──> Point-in-time temporal queries
│
▼
Consolidate (compress) ──> Merge repeated patterns into summaries
│
▼
Decay (cleanup) ──────────> Expire TTL'd facts, evict low-salienceThe context MCP tool returns the most relevant facts ranked by salience, designed for populating an LLM's context window with the most important knowledge.
All params are optional:
| Param | Type | Default | Description |
|---|---|---|---|
maxFacts | number | 100 | Maximum facts to return |
minRelevance | number | 0.0 | Minimum salience score (0.0-1.0) |
predicates | string array | all | Filter to only these predicate types |
scope | string | all scopes | Optional scope filter |
MCP example:
curl -X POST http://localhost:9300/mcp \
-H "Content-Type: application/json" \
-H "X-Database: default" \
-H "X-Tenant-ID: default" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "context",
"arguments": {
"maxFacts": 50,
"minRelevance": 0.1,
"predicates": ["user_preference", "conversation_topic"]
}
}
}'Response (in result.content[0].text):
Context Window (12/47 facts):
Predicates: {user_preference=5, conversation_topic=7}
[salience=0.920] user_preference(alice, dark_mode)
[salience=0.815] conversation_topic(session_42, kotlin)
...The output is pre-sorted by salience. Use this to populate your LLM prompt with the most relevant knowledge without retrieving everything.
The recall MCP tool finds facts that were valid at a specific point in time. It checks validFrom, validUntil, and ttl bounds to determine validity at the given timestamp.
Required params: predicate (string), args (string array), timestamp (epoch milliseconds)
Optional params: scope (string)
MCP example -- "What location was alice at on Jan 1 2024?":
curl -X POST http://localhost:9300/mcp \
-H "Content-Type: application/json" \
-H "X-Database: default" \
-H "X-Tenant-ID: default" \
-d '{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "recall",
"arguments": {
"predicate": "location",
"args": ["alice", "?where"],
"timestamp": 1704067200000
}
}
}'Response (in result.content[0].text):
Found 1 fact(s) valid at timestamp 1704067200000:
location(alice, london) [valid: 1703980800000 -> 1704153600000]Use ?-prefixed variables in args to match any value at that position, just like ask.
Set ttl (in milliseconds) when asserting a fact to make it expire automatically after that duration.
Example -- fact expires after 1 hour (3600000 ms):
curl -X POST http://localhost:9300/mcp \
-H "Content-Type: application/json" \
-H "X-Database: default" \
-H "X-Tenant-ID: default" \
-d '{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "tell",
"arguments": {
"predicate": "session_active",
"args": ["user_42"],
"ttl": 3600000
}
}
}'Use validFrom and validUntil (epoch milliseconds) to create facts that are only valid within a specific time window. These facts exist in the store but only match temporal queries within their validity range.
Example -- meeting scheduled for a specific window:
curl -X POST http://localhost:9300/mcp \
-H "Content-Type: application/json" \
-H "X-Database: default" \
-H "X-Tenant-ID: default" \
-d '{
"jsonrpc": "2.0",
"id": 4,
"method": "tools/call",
"params": {
"name": "tell",
"arguments": {
"predicate": "meeting",
"args": ["standup", "room_3"],
"validFrom": 1704092400000,
"validUntil": 1704094200000
}
}
}'This fact is valid from 1704092400000 to 1704094200000. A recall query with a timestamp inside that range will find it; a timestamp outside will not.
The compress MCP tool merges repeated episodic patterns into semantic summaries. It takes no arguments.
MCP example:
curl -X POST http://localhost:9300/mcp \
-H "Content-Type: application/json" \
-H "X-Database: default" \
-H "X-Tenant-ID: default" \
-d '{
"jsonrpc": "2.0",
"id": 5,
"method": "tools/call",
"params": {
"name": "compress",
"arguments": {}
}
}'Response (in result.content[0].text):
Consolidated 3 pattern(s):
NEW: asked_about_frequently(alice, kotlin)
NEW: asked_about_frequently(alice, coroutines)
NEW: visited_regularly(alice, docs_page)When to use: Run consolidation after many similar events have accumulated (e.g., an agent has queried the same topics repeatedly across a session). Consolidation detects these episodic patterns and creates semantic summary facts, reducing memory bloat while preserving the knowledge signal.
The cleanup MCP tool expires facts past their TTL and evicts facts with salience below a threshold.
Optional param: threshold (number) -- salience floor below which facts are evicted. Default: 0.05.
MCP example:
curl -X POST http://localhost:9300/mcp \
-H "Content-Type: application/json" \
-H "X-Database: default" \
-H "X-Tenant-ID: default" \
-d '{
"jsonrpc": "2.0",
"id": 6,
"method": "tools/call",
"params": {
"name": "cleanup",
"arguments": {
"threshold": 0.05
}
}
}'Response (in result.content[0].text):
Decay complete: 4 expired, 7 evicted (11 total removed)The response distinguishes between:
validUntil has passed.For long-running agent sessions, run this periodic maintenance workflow:
Step 1: compress (no arguments)
Step 2: cleanup (threshold: 0.05)Why this order matters: Consolidation runs first to detect repeated patterns and merge them into summary facts before decay runs. If you run decay first, low-salience episodic facts might be evicted before consolidation can detect the patterns they form. Running compress first preserves the knowledge signal in summary form, then cleanup safely removes the remaining noise.
Recommended cadence: Every 100-500 fact assertions, or at natural session boundaries.
Manually boost a fact's salience priority. This is REST-only -- there is no MCP tool for priority boosting.
Endpoint: POST /memory/priority
Required fields: predicate (string), args (string array), priority (number, 0.0-1.0)
Optional fields: truthVal (bool, default true), scope (string)
curl -X POST http://localhost:9300/memory/priority \
-H "Content-Type: application/json" \
-H "X-Database: default" \
-H "X-Tenant-ID: default" \
-d '{
"predicate": "user_preference",
"args": ["alice", "dark_mode"],
"priority": 0.95
}'Response: Priority set: 0.95 for user_preference(alice, dark_mode)
Boosting priority directly increases the fact's composite salience score, making it more likely to appear in context windows and less likely to be evicted by decay.
Subscribe to real-time knowledge change events via Server-Sent Events (SSE).
Endpoint: GET /memory/events
Required header: X-Tenant-ID (events are tenant-scoped)
Optional query params:
predicate -- filter to events about a specific predicateevents -- comma-separated event types to subscribe to (default: all)since -- event ID to replay missed events fromEvent types:
| Type | Fires When |
|---|---|
fact_asserted | A new fact is added to the knowledge base |
fact_retracted | A fact is removed (explicitly or via truth maintenance) |
fact_expired | A fact's TTL or validUntil has passed |
rule_asserted | A new rule is taught |
consolidation | Episodic patterns are consolidated into summary facts |
Example:
curl -N http://localhost:9300/memory/events \
-H "X-Database: default" \
-H "X-Tenant-ID: default"Event format (SSE data: lines):
{"type":"fact_asserted","eventId":42,"timestamp":1704067200000,"atom":{"predicate":"likes","args":["alice","bob"]}}
{"type":"fact_expired","eventId":43,"timestamp":1704070800000,"atom":{"predicate":"session_active","args":["user_42"]}}Use event streaming for reactive agent architectures that need to respond to knowledge changes in real time.
1704067200000, not strings like "2024-01-01T00:00:00Z". The parameter is a JSON number.prioritize MCP tool. You must use POST /memory/priority with the standard HTTP headers.X-Tenant-ID header on the SSE connection determines which tenant's events you receive. There is no way to specify tenant in query params.0.05 removes facts scoring under 0.05. Setting it to 0.0 only expires TTL-past facts without salience-based eviction.minRelevance (mapped to minSalience internally). The REST endpoint /memory/context uses minSalience in the request body.ttl param on tell is milliseconds from assertion time (e.g., 3600000 = 1 hour from now). Use validUntil for an absolute epoch deadline.~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.