Skip to content

ADR-0007 — Apache AGE as Property Graph Layer on PostgreSQL

Status: Accepted
Date: 2026-05-24
Author: João Luís Brazão
Deciders: João Luís Brazão
Vikunja: #838


Context

The Down a Rabbit Hole second brain holds 15,000+ knowledge nodes with rich relational structure: nodes link to other nodes (co-citation, similarity, contradiction), nodes mention entities (people, organisations, concepts), and entities relate to each other (LEADS, MEMBER_OF, PARTY_TO, etc.).

The existing pgvector layer enables semantic similarity search (RAG) but cannot answer graph traversal questions such as: - "What nodes are within 2 hops of this concept?" - "Which entities appear together most often?" - "Find all paths between Ukraine War and Financial Fragmentation."

An additional graph capability was needed without introducing a separate database system (Neo4j, ArangoDB) that would break the sovereignty-first and single-datastore principles established in ADR-001 and ADR-003.


Decision

Add Apache AGE 1.5.0 (A Graph Extension) to the existing PostgreSQL 16 instance alongside pgvector 0.8.2. AGE implements the openCypher query language natively inside PostgreSQL, so graph queries run as standard SQL with SELECT * FROM cypher(...) syntax.

The custom Docker image localhost/infra-postgres-age:16-age1.5.0 is built from pgvector/pgvector:pg16 (to preserve the pgvector base) with AGE compiled from source and shared_preload_libraries = 'age' injected into postgresql.conf.sample.

The named graph down_rabbit_hole_graph holds three vertex labels: - KnowledgeNode — mirrors public.nodes - Entity — mirrors entity types from ontology_entity_types - Topic — taxonomy terms acting as concept hubs

And three edge labels: - LINKS_TO — from node_links table - MENTIONS — from node_entity_mentions table - TAGGED_WITH — node → taxonomy term associations

The brain.graph-rebuild skill populates the graph from the relational tables. It runs on-demand and via the weekly cron (Sunday 04:30 UTC).


Alternatives Considered

Option Reason Rejected
Neo4j Separate process, breaks single-datastore principle (ADR-001)
ArangoDB Same — separate process + different query language
PostgreSQL recursive CTEs Can traverse but no property graph model, no Cypher
PostGIS graph extension Geographic focus, not semantic graph
Apache AGE (chosen) Runs inside PostgreSQL; openCypher; MIT licence; active community

Consequences

Positive: - Zero new infrastructure — AGE lives inside the existing infra-postgres container - Graph queries use standard PostgreSQL connections (same DSN, same auth) - openCypher is industry-standard (Neo4j-compatible syntax) - Enables future DARH graph traversal skills without external dependencies

Negative / Risks: - AGE is a PostgreSQL extension, not a native graph DB — performance ceiling exists for very large graphs (>10M edges); acceptable for current scale - Must rebuild infra-postgres image if AGE needs upgrading (no ALTER EXTENSION) - AGE requires CREATE EXTENSION age per database; new databases need explicit migration step - Existing volumes work unchanged — AGE does not modify base PostgreSQL storage


Migration Notes

  • Compose backup: /opt/infrastructure/docker-compose.yml.bak-pre-age-20260524T200101Z
  • DB dump pre-migration: /opt/backups/pre-age-20260524-193501.sql.gz (771 MB)
  • SQL migration: /opt/infrastructure/sql-migrations/20260524_subsystem1.sql
  • Rollback: restore compose + dump; no data loss since AGE data is derived from relational tables and can be rebuilt by brain.graph-rebuild

  • ADR-001: PostgreSQL + pgvector as single datastore → AGE extends this
  • ADR-003: Sovereignty boundary → AGE maintains it (same VPS, same container)
  • ADR-005: Skills Gateway → brain.graph-rebuild skill populates graph