6 · Storage & Pipeline
Storage implementations
Three concrete store types, each implementing a kernel Protocol:
PgVectorStore
Raw SQL implementation (no SQLAlchemy ORM model — pure engine.begin() / engine.connect()). Requires the pgvector PostgreSQL extension.
from substrate.capabilities.vector import PgVectorStore
store = PgVectorStore(
session_factory=session_factory,
engine=engine,
dimensions=384, # must match the embedding model
)
await store.ensure_table() # CREATE TABLE IF NOT EXISTS + indexes
ids = await store.add(documents, collection="kb")
results = await store.search(query_vec, collection="kb", limit=5)
await store.delete(ids, collection="kb")Schema (created by ensure_table()):
CREATE TABLE IF NOT EXISTS vector_store_{collection} (
id TEXT PRIMARY KEY,
text TEXT NOT NULL,
content_json JSONB NOT NULL,
metadata JSONB NOT NULL DEFAULT '{}',
embedding VECTOR({dimensions}),
created_at TIMESTAMPTZ DEFAULT now()
);
CREATE INDEX ON vector_store_{collection} USING hnsw (embedding vector_cosine_ops);Each collection is a separate table — no cross-collection queries.
AGEGraphStore
Uses raw asyncpg connections to execute openCypher via the Apache AGE cypher() function. The graph is created automatically on first connect.
from substrate.capabilities.graph import AGEGraphStore
from substrate.kernel.storage.graph import Entity, Relationship
store = AGEGraphStore(
dsn="postgresql://postgres:postgres@localhost/agentdb",
graph_name="knowledge",
)
await store.connect()
await store.add_entities([
Entity(label="Person", properties={"name": "Alice", "role": "Engineer"}),
])
await store.add_relationships([
Relationship(source_label="Person", source_key="Alice",
target_label="Company", target_key="Acme",
rel_type="WORKS_AT"),
])
subgraph = await store.get_subgraph(entities=["Alice"], depth=2)
results = await store.query_cypher("MATCH (n:Person) RETURN n")S3FileStore
Delegates to MinIOConnector (from infrastructure/storage/minio.py). Compatible with MinIO in docker-compose and AWS S3 in production.
from substrate.capabilities.storage import S3FileStore
store = S3FileStore(
endpoint_url="http://localhost:9000",
access_key="minioadmin",
secret_key="minioadmin",
bucket="agent-files",
)
await store.connect() # also creates the bucket if missing
await store.put("agent-files", "reports/2024-01.pdf", pdf_bytes)
data: bytes = await store.get("agent-files", "reports/2024-01.pdf")
await store.delete("agent-files", "reports/2024-01.pdf")PipelineEngine
PipelineEngine (capabilities/pipeline/engine.py) executes declarative pipelines — named sequences of tool calls with $prev variable passing between steps.
PipelineStep fields
| Field | Type | Description |
|---|---|---|
adapter_name | str | Tool name in the Toolbox |
action | str | Method to call (default: "execute") |
input_mapping | dict | Literal values or $prev.content / $step_0.content refs |
output_key | str | Key for result in context dict (default: step_{i}) |
timeout | int | Per-step timeout in seconds (default: 60) |
$-reference resolution
# Step 0 outputs to context["report"]
PipelineStep(adapter_name="postgres_query",
input_mapping={"sql": "SELECT * FROM events"},
output_key="report")
# Step 1 reads previous step's text via $prev.content
PipelineStep(adapter_name="email_sender",
input_mapping={"to": "user@example.com",
"body": "$prev.content"})_resolve_inputs() walks the $-path through the context dict. Literal values pass through unchanged.
DataRefStore
DataRefStore (capabilities/pipeline/data_ref.py) is a hybrid Redis/S3 pointer store for large data exchange between pipeline steps — prevents large payloads from bloating the LLM context window.
| Method | Effect |
|---|---|
store(data) | Routes to Redis or S3 based on size; returns DataRef |
resolve(ref) | Fetches from the right backend |
pin(ref) | Removes TTL (prevents expiry for long jobs) |
unpin(ref) | Re-enables TTL |
delete(ref) | Manual cleanup |
cleanup_expired() | Sweeps stale S3 refs (Redis self-expires) |
DataRefArtifactStore wraps DataRefStore to satisfy the kernel ArtifactStore protocol used by ToolInvoker and ToolChainTool — ref IDs are plain strings at that boundary.