| name | engine-memory-layer |
| description | Use this skill whenever the user asks about using StixDBEngine directly for memory management, agentic context, reasoning, chat, streaming, or advanced memory operations. Covers engine.store(), engine.ask(), engine.chat(), engine.stream_chat(), engine.retrieve(), engine.ingest_file(), engine.recursive_chat(), engine.stream_recursive_chat(), and all other engine-level operations. Use when the user is working with the Python library (not the CLI or raw REST calls). |
| compatibility | Requires stixdb-engine Python package, Python 3.10+, a running StixDB server (stixdb daemon start), async/await support |
StixDB Engine API Skill
StixDBEngine is a REST client that connects to a running StixDB server and exposes a clean async Python API for all StixDB operations. It does not run anything in-process — all work is delegated to the server over HTTP.
Prerequisite: start the server first with stixdb daemon start (or stixdb serve).
Quick Start
from stixdb import StixDBEngine, StixDBConfig
engine = StixDBEngine()
await engine.store("my_agent", content="Alice leads the payments team.")
results = await engine.retrieve("my_agent", "payments lead", top_k=5)
response = await engine.ask("my_agent", "Who leads payments?")
print(response.answer)
async with StixDBEngine() as engine:
response = await engine.ask("my_agent", "Who leads payments?")
print(response.answer)
config = StixDBConfig(url="http://localhost:4020", api_key="your-key")
async with StixDBEngine(config=config) as engine:
...
config = StixDBConfig.from_env()
async with StixDBEngine(config=config) as engine:
...
Configuration
StixDBConfig holds only connection settings. The server manages all storage, LLM, and embedding config.
from stixdb import StixDBConfig
config = StixDBConfig(
url="http://localhost:4020",
api_key="your-secret-key",
timeout=120.0,
)
From environment (recommended):
config = StixDBConfig.from_env()
Environment variables:
STIXDB_URL=http://localhost:4020
STIXDB_API_KEY=your-secret-key
STIXDB_TIMEOUT=120
Inline construction (no config object needed):
engine = StixDBEngine(url="http://prod-server:4020", api_key="sk-...")
Storing Memories
engine.store()
result = await engine.store(
collection="agent_name",
content="The memory text.",
node_type="fact",
tier="episodic",
importance=0.7,
pinned=False,
source="module_name",
source_agent_id="worker_1",
tags=["tag1", "tag2"],
metadata={"custom": "value"},
node_id="optional_custom_id",
)
Importance guide:
| Score | Use for |
|---|
0.95 | In-progress state, user preferences, critical decisions |
0.9 | Bug fixes, patterns, architecture rules |
0.85 | Module maps, API surfaces, session summaries |
0.7 | Normal facts (default) |
0.5 | Background / historical context |
engine.bulk_store()
items = [
{"content": "Fact 1", "tier": "semantic", "importance": 0.8, "tags": ["a"]},
{"content": "Fact 2", "tier": "episodic", "importance": 0.5},
]
result = await engine.bulk_store(collection="agent_name", items=items)
Each item accepts the same fields as store(). Use for batch imports and initialization.
Ingesting Files
engine.ingest_file()
result = await engine.ingest_file(
collection="agent_name",
filepath="/path/to/document.pdf",
tags=["docs", "v1"],
chunk_size=600,
chunk_overlap=150,
)
Supported types: .pdf, .md, .rst, .html, .txt, .csv, .json, .jsonl, .yaml, .toml, .py, .js, .ts, .go, .rs, .sql, and more. Binary files are skipped automatically.
Deduplication is built in — re-ingesting the same file doesn't create duplicate chunks.
engine.ingest_folder()
result = await engine.ingest_folder(
collection="agent_name",
folderpath="/path/to/docs",
tags=["documentation"],
chunk_size=600,
chunk_overlap=150,
recursive=True,
)
Respects .gitignore. Skips node_modules, .git, and binary files automatically.
Chunk size guidance:
- Technical docs / code:
500–800
- Prose / narrative:
1000–1500
- Always keep overlap at ~20% of chunk size
Retrieval
engine.retrieve() — raw retrieval, no LLM
nodes = await engine.retrieve(
collection="agent_name",
query="user preferences",
top_k=10,
threshold=0.1,
depth=1,
mode="hybrid",
)
Retrieval modes:
| Mode | How it works | When to use |
|---|
"hybrid" (default) | 0.7 × semantic_score + 0.3 × keyword_score | Best general recall |
"keyword" | Tag overlap + content term match. No embedding API call. ~5ms | Fast exact-term lookups |
"semantic" | Vector embedding + cosine similarity | Paraphrase / conceptual queries |
Use retrieve() when you want ranked nodes without LLM cost. Use ask() when you need synthesis.
Asking Questions (LLM Reasoning)
engine.ask() — single-pass reasoning
response = await engine.ask(
collection="agent_name",
question="What are the user's accessibility needs?",
top_k=15,
threshold=0.2,
depth=2,
thinking_steps=1,
hops_per_step=4,
system_prompt=None,
output_schema=None,
max_tokens=None,
)
ContextResponse fields:
response.answer
response.reasoning_trace
response.sources
response.confidence
response.model_used
response.latency_ms
response.is_complete
response.suggested_query
for src in response.sources:
print(src["content"], src.get("relevance"))
Answer format: ask() returns rich Markdown with inline citations [1], [2] matching numbered sources, and a Sources section at the end. Pass response.answer directly to a Markdown renderer.
Multi-hop reasoning (thinking_steps > 1)
response = await engine.ask(
collection="agent_name",
question="Summarise all known bugs and their root causes.",
thinking_steps=3,
hops_per_step=4,
top_k=25,
depth=3,
)
At each step the LLM decides what to search next. Higher thinking_steps = deeper answers at the cost of more LLM calls.
Chat (Conversational, with Session History)
engine.chat() — single turn with session memory
response = await engine.chat(
collection="agent_name",
question="Help me understand the auth flow.",
session_id="conv_123",
top_k=15,
depth=2,
temperature=None,
max_tokens=None,
)
print(response.answer)
session_id groups messages into a conversation. The history is passed to the LLM for context on each turn.
engine.recursive_chat() — multi-hop single question
response = await engine.recursive_chat(
collection="agent_name",
question="What are the key revenue drivers and which accounts are at risk?",
session_id="conv_123",
thinking_steps=2,
hops_per_step=4,
threshold=0.7,
temperature=None,
max_tokens=None,
)
Use when you want the engine to autonomously refine its retrieval across multiple hops before synthesising a final answer.
Streaming
engine.stream_chat() — stream tokens from a single-turn chat
async for chunk in engine.stream_chat(
collection="agent_name",
question="Explain the auth flow.",
session_id="conv_123",
top_k=15,
depth=2,
temperature=None,
max_tokens=None,
):
if chunk.get("type") == "node_count":
print(f"Retrieved {chunk['count']} nodes")
elif chunk.get("type") == "answer":
print(chunk["content"], end="", flush=True)
Chunk format: each chunk is a dict:
type | content / extra fields | When emitted |
|---|
"node_count" | count: int | Once, before first token |
"answer" | content: str — token(s) | Repeatedly as the LLM generates |
The stream ends when the async iterator is exhausted (no sentinel needed from the caller side).
engine.stream_recursive_chat() — stream tokens from multi-hop reasoning
async for chunk in engine.stream_recursive_chat(
collection="agent_name",
question="Deep dive on the storage architecture.",
session_id=None,
thinking_steps=2,
hops_per_step=4,
temperature=None,
max_tokens=None,
):
if chunk.get("type") == "thinking":
print(f"[thinking] {chunk['content']}")
elif chunk.get("type") == "answer":
print(chunk["content"], end="", flush=True)
Additional chunk type for recursive streaming:
type | Meaning |
|---|
"thinking" | Narration emitted before each hop |
"node_count" | Nodes retrieved in this hop |
"answer" | Final answer token |
Memory Tiers
| Tier | Purpose | Example |
|---|
episodic | Specific events, recent interactions | "User clicked dark mode at 15:30Z" |
semantic | Stable facts, learned preferences | "User prefers dark mode" |
procedural | How-to steps, workflows | "Reset password: click forgot → check email → follow link" |
working | Auto-promoted hot facts (managed by the agent) | Summary nodes created during consolidation |
Node Types
| Type | Use for |
|---|
fact | Static assertions — "The office is on 5th Street" |
experience | Events with timestamp — "User visited at 15:30Z" |
goal | Objectives — "User wants fewer notifications" |
rule | Conditional logic — "If offline, queue messages" |
pattern | Recurring behaviours — "User logs in at 9am daily" |
Graph Operations
await engine.add_relation(
collection="agent_name",
from_node="node_uuid_a",
to_node="node_uuid_b",
relation_type="supports",
metadata={"strength": 0.8},
)
stats = await engine.get_graph_stats(collection="agent_name")
stats = await engine.get_collection_stats(collection="agent_name")
result = await engine.dedupe_collection(collection="agent_name", dry_run=False)
Collection Management
await engine.store("new_collection", content="First memory.")
collections = engine.list_collections()
collections = await engine.list_collections_async()
await engine.drop_collection(collection="agent_name")
result = await engine.delete_collection(collection="agent_name")
Background Agent
result = await engine.trigger_agent_cycle(collection="agent_name")
status = await engine.get_agent_status(collection="agent_name")
What a cycle does:
- Merges nodes above
0.88 cosine similarity into a summary node
- Collapses exact duplicates (highest importance wins)
- Decays archived nodes with a 48-hour half-life
- Prunes nodes below
0.05 importance
The cycle runs automatically in the background every 30 seconds (configurable via STIXDB_AGENT_CYCLE_INTERVAL on the server).
Observability
traces = engine.get_traces(collection="agent_name", limit=10)
for trace in traces:
print(f"{trace['timestamp']}: {trace['message']}")
Error Handling
try:
response = await engine.ask(collection="agent", question="...?")
except httpx.ConnectError:
print("Server not running — start with: stixdb daemon start")
except httpx.HTTPStatusError as e:
print(f"Server error {e.response.status_code}: {e.response.text}")
Common causes:
| Error | Cause | Fix |
|---|
ConnectError | Server not running | stixdb daemon start |
401 Unauthorized | Wrong or missing API key | Set STIXDB_API_KEY to match server |
404 Not Found | Wrong collection name or URL | Check stixdb collections list |
empty answer | top_k too low or question too generic | Increase top_k, be more specific |
API Reference
| Method | Returns | Purpose |
|---|
engine.store(collection, content, ...) | dict | Store a single memory node |
engine.bulk_store(collection, items) | dict | Store many nodes efficiently |
engine.ingest_file(collection, filepath, ...) | dict | Parse and chunk a file |
engine.ingest_folder(collection, folderpath, ...) | dict | Process a directory recursively |
engine.retrieve(collection, query, mode="hybrid", ...) | list[dict] | Raw retrieval without LLM |
engine.ask(collection, question, thinking_steps=1, ...) | ContextResponse | LLM-synthesised Markdown answer with citations |
engine.chat(collection, question, session_id=None, ...) | ContextResponse | Single-turn chat with session history |
engine.stream_chat(collection, question, ...) | AsyncIterator[dict] | Stream answer tokens chunk-by-chunk |
engine.recursive_chat(collection, question, thinking_steps=2, ...) | ContextResponse | Multi-hop autonomous reasoning |
engine.stream_recursive_chat(collection, question, ...) | AsyncIterator[dict] | Stream multi-hop reasoning with thinking narration |
engine.add_relation(collection, from_node, to_node, ...) | dict | Create an explicit graph edge |
engine.get_graph_stats(collection) | dict | Node/edge counts |
engine.get_collection_stats(collection) | dict | Tier breakdown |
engine.dedupe_collection(collection, dry_run) | dict | Remove duplicate nodes |
engine.trigger_agent_cycle(collection) | dict | Run consolidation/decay cycle manually |
engine.get_agent_status(collection) | dict | Background agent state |
engine.drop_collection(collection) | — | Unload from memory (data kept) |
engine.delete_collection(collection) | dict | Permanently delete all data |
engine.list_collections() | list[str] | All known collections (sync) |
engine.list_collections_async() | list[str] | All known collections (async) |
engine.get_traces(collection, limit) | list[dict] | Recent operation traces |
Patterns
Pattern 1 — Knowledge base with Q&A
from stixdb import StixDBEngine
async with StixDBEngine() as engine:
await engine.ingest_folder("kb", folderpath="./docs", tags=["official"])
response = await engine.ask(
"kb",
"How do I reset my password?",
top_k=20,
depth=2,
)
print(response.answer)
Pattern 2 — Streaming chat to a UI
from stixdb import StixDBEngine, StixDBConfig
config = StixDBConfig.from_env()
async def stream_to_client(collection: str, question: str):
async with StixDBEngine(config=config) as engine:
async for chunk in engine.stream_chat(collection, question=question):
if chunk.get("type") == "answer":
yield chunk["content"]
Pattern 3 — Multi-agent shared context
from stixdb import StixDBEngine
async with StixDBEngine() as engine:
await engine.store("shared", content="Found bug in payment processor", tags=["bugs"], source="agent_1")
await engine.store("shared", content="Root cause: timeout on retry", tags=["bugs"], source="agent_2")
response = await engine.ask("shared", "What bugs were found and what caused them?", top_k=20)
print(response.answer)
Pattern 4 — Deep reasoning with multi-hop
from stixdb import StixDBEngine
async with StixDBEngine() as engine:
response = await engine.recursive_chat(
"proj_myapp",
question="Summarise all architecture decisions and their consequences.",
thinking_steps=3,
hops_per_step=4,
)
print(response.answer)
print(response.reasoning_trace)
Pattern 5 — Connect to remote server
from stixdb import StixDBEngine, StixDBConfig
config = StixDBConfig(
url="https://stixdb.internal.mycompany.com",
api_key="prod-secret-key",
timeout=60.0,
)
async with StixDBEngine(config=config) as engine:
response = await engine.ask("prod_agents", "What is the current deployment status?")
print(response.answer)