Develop: DataHub templates
How to author a DataHub template — the opt-in domain model that gives a client's tenant a head start on top of the free-form primitives. The architecture decision is ADR-0002 (docs/adr/0002-template-architecture.md); the in-repo guide is docs/templates/authoring-guide.md; the worked example is packages/datahub/src/datahub/templates/examples/hr_skills/ (an example template — its HR domain is incidental; AcasoOS is domain-general).
The two-layer model
Every tenant always has the platform ontology (acaso_core.ttl): Person, Statement, SourceDocument and friends, addressable in the tenant's knowledge-graph namespace. A template layers a curated domain module on top — an OWL vocabulary (module.ttl), source pipelines, tools, and cultivators — that tenants opt into per tenant via tenant_template_state. Templates evolve independently on their own semver; tenants pin a version.
Layout
packages/datahub/src/datahub/templates/<family>/<name>/
├── __init__.py # side-effect imports pipelines (and cultivators)
├── module.ttl # OWL vocabulary (classes + properties)
├── pipelines/
│ ├── __init__.py # side-effect imports each pipeline
│ └── from_slack.py # one module per data source
├── cultivators/ # optional — Kinds shipped with the template
├── tools.py # tools the template exposes to agents
└── README.md<family> groups domains (sales, eng, hr, …); <name> is the template. Three sibling locations share this internal shape:
templates/<family>/<name>/productized catalog templates, reusable across clients. None ship in-tree yet; each lands via its own spec cycle.templates/custom/<client_slug>/client-bespoke work that doesn't generalize, namespaced to one tenant.templates/examples/<name>/reference implementations (examples/hr_skills,examples/perf_evidence). Copy them to start; activate them only for dev/demo tenants, never for clients.
A blank skeleton to copy lives at packages/datahub/src/datahub/templates/_template_skeleton/.
Steps
- Spec first. Templates ship via the spec process (
specs/): which classes, object properties, pipelines, and tools; the datatype properties per class; whether any new SQL is needed (rare — templates normally live entirely in the KG). - Define the vocabulary in
module.ttlas an OWL module (classes, object/datatype properties, bilingual labels,acaso:embeddablewhere search should see the class). People-like classes subclassacaso:Person— the erasure flow depends on it. - Author pipelines under
pipelines/. A pipeline consumes ingest events and writes graph structure. Every write goes throughdatahub.kg.individuals(upsert_individual/link) — deterministic IRIs keep re-ingest idempotent, and embeddable classes enqueue their vectors automatically. End each module withregister_pipeline(PipelineDescriptor(...)). - Author tools in
tools.py— tenant-scoped query functions over the template's entities. Agents consume them today by wrapping them in@toolfunctions (theknowledge = ["<family>/<name>"]auto-wiring is registry metadata in v0, not active tool injection). - Ship cultivators under
cultivators/if the template should write memory autonomously — see Develop: Cultivators. - Activate per tenant through the admin API:
curl -sS -X POST https://<host>/v1/admin/tenants/<slug>/templates \
-H "Authorization: Bearer $ADMIN_KEY" -H "Content-Type: application/json" \
-d '{"template_key": "<family>/<name>", "version": "1.0", "config": {}}'- Test end-to-end under
packages/datahub/tests/integration/against the docker-compose Postgres (AGE + pgvector): event in, nodes/edges out, both stores consistent, RLS isolation asserted.
Migrations
If the template genuinely needs new tables, the alembic migration ships in the same PR. tenant_id columns plus ENABLE/FORCE ROW LEVEL SECURITY and a tenancy policy are mandatory; CI's tenancy lint blocks tables without them. Migrations are append-only and must survive make migration-check (upgrade → downgrade → upgrade).
Versioning
Templates follow semver, pinned per tenant in tenant_template_state.version:
- Backward-compatible additions (new entity types, new optional fields) — same MINOR.
- Entity-shape changes (rename, drop, type change) — version bump plus a migration script for existing tenant data.
Ship checklist
- Spec exists; layout matches the skeleton; entity/edge constants exported.
- All graph writes via
pipelines.nodes; no direct AGE writes; no raw cross-tenant queries. - Integration tests green against compose Postgres; all
makegates green. - Migration (if any) carries RLS and passes
make migration-check. - Tenant activation recorded for each client that opts in.