Fabriq
1.x
Docs/Fabriq/Introduction
Open

Reading8 min
Updated2 Aug 2026
Sourcev1/index.mdx

Fabriq is the single data fabric for your application and its agents. Application code never opens a connection, writes SQL, or calls Redis directly — it holds one facade and reaches every store through typed capability ports. AI agents reach the same fabric as a brain: fused semantic, lexical, and graph recall, guarded memory formation, and a live watch stream — in-process or over MCP. Both ride one small set of invariants that are otherwise impossible to hold across many engines.

The three invariants01

Architecture02

Postgres is the source of truth. Redis is the event fan-out. FalkorDB and Elasticsearch are derived projections. The kernel in core/ is domain-agnostic and engine-agnostic; engine dialects live exclusively under adapters/.

The layered fabriq architectureApplication code and AI agents hold one facade over the engine-agnostic core kernel — registry, command, event, projection, subscribe, query, cache, blob, and agent — which reaches the capability ports, the adapter dialects (including trove for blob CAS and grove-kv for the cache), and the backing engines, with Postgres the source of truth.APPLICATIONYour applicationone facade · query.FabricAI agentsagent toolkit · recall · distillCOREfabriq · core/ — engine- & domain-agnostic kernelregistrycommandeventprojectionsubscribequerycacheblobagent+ lifecycle hooks · validate · upcasters · dynamic entities · structural tenancyPORTScapability ports — relational · graph · search · vector · spatial · timeseries · document · cache · blob (one typed port each, no shared DSL)ADAPTERSpostgres · groveredisfalkordbelastictrovegrove-kvENGINESPostgres+ Timescale · pgvector · PostGISsource of truth · outboxRedisevent fan-out · cacheFalkorDBgraph projectionElasticsearchsearch projectionBlob storefilesystem · S3 · troveCAS · file plane

Capability ports03

There is deliberately no unified query language. Each storage capability has an explicit, engine-typed port. Relational work speaks SQL through grove, graph work speaks openCypher, search speaks queries over declared fields. No engine types appear in any signature, so adapters stay swappable.

PortEnginePurpose
RelationalPostgres (grove)The source of truth: aggregate rows, raw SQL escape hatch.
GraphFalkorDBKnowledge-graph projection, openCypher traversal + batched hydration.
SearchElasticsearchFull-text projection over declared fields.
TimeseriesTimescaleDBBulk telemetry ingest and windowed reads (event-bypass).
VectorpgvectorEmbedding upsert and nearest-neighbour search.
SpatialPostGISGeometry upsert/delete and GiST-accelerated radius search. WKT+SRID, engine-neutral. SRID 4326 → true metres (geography); other SRIDs → planar metres.
DocumentPostgres + grove CRDTCollaborative CRDT documents that materialize into ordinary events.
BlobObject store (via Trove)External byte storage as f.Blob(): put/get/head/delete/list/copy, with presign, multipart, and range detected per driver, plus content-addressable dedup. Bytes never touch Postgres.

Reads over opted-in entities are transparently cached — a two-level (per-id rows + query result-sets), write-invalidated read-through cache behind these ports, with an optional per-node in-process L1. Off by default; a registration flag, not a code change.

Files and blobs04

Beyond structured records, fabriq has a full file plane. The catalog — files, folders, permissions, shares, and bookmarks — lives in Postgres as ordinary tenant-scoped entities, so it keeps every fabriq invariant: versioned events, RLS, graph and search projections, and live queries. The raw bytes live in an external object store reached through the f.Blob() port and never touch Postgres — content-addressed, reference-counted, and reclaimed by a reconciler-driven garbage collector. It is shipped dark: the blob port is nil until a storage driver is configured.

Note

ADR 0008 amends ADR 0007 with one bounded exception — referenced external blobs — fenced by three disciplines: opaque (no catalog authority), checksum-verifiable, and reconciled rather than rebuilt. See the File Plane overview.

Cross-tenant analytics05

Catalog mode physically isolates each tenant in its own database — which makes fleet-wide reporting impossible with an ordinary query. The optional analytics sink closes that gap: a third projection, peer to graph and search, that consumes the same shared event stream and materializes a denormalized, cross-tenant read model in one Postgres database keyed by tenant_id. Operators run fleet-wide SQL without ever touching a per-tenant database, in single, sharded, and catalog modes alike.

It is the one place fabriq deliberately co-locates data from many tenants, so it is fenced as a trust boundary: deny-by-default (an aggregate is analyticized only when explicitly marked), field-level redaction (only allow-listed columns cross), a separate database and credential (rejected at boot if it collides with a tenant DSN), and tenant_id NOT NULL on every row. Off until Config.Analytics is set.

Warning

The analytics store is operator-only: it has no RLS and holds many tenants' data side by side. Never expose it to tenant-facing surfaces, and mark only the fields that genuinely need fleet-wide reporting. See the Analytics sink trust-boundary section.

A first taste06

reg := registry.New()
_ = domain.RegisterAll(reg) // or your own entity pack

f, stores, err := fabriq.Open(ctx, reg, fabriq.Config{
    Postgres: fabriq.PostgresConfig{DSN: dsn},
    Redis:    fabriq.RedisConfig{Addr: redisAddr},
})

// Writes: the only path, one versioned event per command.
res, err := f.Exec(tenantCtx, command.Command{
    Entity: "asset", Op: command.OpCreate,
    Payload: &domain.Asset{Name: "Pump 7", SiteID: siteID},
})

// Reads: capability ports.
var a domain.Asset
err = f.Relational().Get(tenantCtx, "asset", res.AggID, &a)

// Live deltas: server-resolved channel, conflated, resumable.
deltas, err := f.Subscribe(tenantCtx, query.SubscribeScope{
    Entity: "asset", Scope: "site", ID: siteID,
})

// Live query: a maintained, ordered, filtered window with
// enter/leave/move/update deltas and exact top-N.
snap, live, cancel, err := f.LiveQuery(tenantCtx, livequery.LiveQuery{
    Entity: "asset",
    Where:  query.Where{query.Eq("kind", "pump")},
    Sort:   []livequery.SortKey{{Column: "name"}},
    Limit:  50,
})
Warning

Every call requires a tenant-stamped context (tenant.WithTenant), set only by auth middleware from validated claims. An unstamped context is rejected before it reaches a store.

Embed or serve07

By default a service embeds fabriq and owns its datastore pools. An optional remote topology lets backend services instead talk to a central, connection-owning fabriq over gRPC — through the same query.Fabric interface, so call sites are identical.

Note

The remote protocol is experimental / in development: write, read, subscribe, live-query, blob, and the graph/search/vector retrieval channels (so an agent's recall works remotely) are wired and tested over real gRPC + mTLS. Interactive transactions are a non-goal (ExecBatch is the transaction). See Remote protocol.

An agent's brain08

An AI agent can use fabriq as its brain through the agent toolkit: one recall(query, budget) that fuses semantic, lexical, and graph retrieval into a token-budgeted context pack; guarded memory formation with auto-embedding on write; and a live watch stream — exposed in-process to Go agents and over MCP to any agent.

tk, _ := agent.NewToolkit(f, f.Registry(), embedder, agent.Config{})
pack, _ := tk.Recall(tenantCtx, agent.RecallRequest{
    Query: "overheating pumps at the north site", Budget: 8000,
    Entities: []string{"asset", "note"},
})

The admin console09

Fabriq ships a mountable, plugin-based web admin console over a running instance — one interactive surface per subsystem: browse and edit entities, author schemas, run text / semantic / hybrid search, traverse the graph, manage files and CRDT documents, inspect the outbox, and run read-only SQL — all tenant-scoped.

The fabriq admin console

It reaches an instance through the adminapi HTTP surface, so any client can connect the same way: with a portable connection stringfabriq://<key>@host/<tenant> — consumed by connect() in TypeScript and a full adminapi-mirror client package in Go. Both are verified by an opt-in authentication layer: per-tenant API keys for machine clients, and a username/password dashboard login for humans that mints a short-lived session token validated by the same key middleware.

Where to go next10

Fabriq is built on the Forge ecosystem: storage on grove, binaries on forge (apps) and forge/cli.