Objects with actual meaning
Model the nouns your operators recognize—not anonymous tables leaking through an API.
Without the Palantir-scale everything.
Keep the powerful bit: a living model of your objects, relationships, actions, and questions. Lose the platform migration, the sprawling stack, and the enterprise ceremony. Open Ontology is the idea compressed into a TypeScript package.
const WorkOrder = Ontology.ObjectType(
"WorkOrder",
{
properties: {
title: Property.make(
":work-order/title",
Schema.String,
).required(),
status: Property.make(
":work-order/status",
Schema.String,
).required(),
},
},
);
const ontology = defineOntology(
{ name: "field-service" },
[WorkOrder, AssignedTo, Assign],
);Most schemas describe storage. An ontology describes the world your software acts on—and keeps objects, relationships, rules, actions, and questions in one executable model.
npm install, not a transformation program.
Start with ordinary Effect Schema. Add first-class links, atomic actions, and named Datalog. The field-service experiment below is small enough to understand in one sitting—and complete enough to execute end to end.
import { Effect, Schema } from "effect";
import { KvTriples } from "@triplex-build/triplex";
import {
Change,
Input,
Ontology,
Property,
defineOntology,
makeOntologyRuntime,
} from "@open-ontology/ontology";
const Technician = Ontology.ObjectType("Technician", {
properties: {
name: Property.make(":technician/name", Schema.String).required(),
region: Property.make(":technician/region", Schema.String),
},
});
const WorkOrder = Ontology.ObjectType("WorkOrder", {
properties: {
title: Property.make(":work-order/title", Schema.String).required(),
status: Property.make(":work-order/status", Schema.String).required(),
priority: Property.make(":work-order/priority", Schema.Number),
},
});
const AssignedTo = Ontology.LinkType("assigned-to", {
from: WorkOrder,
to: Technician,
properties: {
assignedAt: Property.instant(":assigned-to/assigned-at").required(),
},
});const AssignWorkOrder = Ontology.ActionType("assign-work-order", {
input: Schema.Struct({
workOrder: Schema.String,
technician: Schema.String,
}),
changes: [
Change.set(
Input.value("workOrder"),
WorkOrder.properties.status,
"assigned",
),
Change.link(
AssignedTo,
Input.value("workOrder"),
Input.value("technician"),
{ ":assigned-to/assigned-at": Input.now },
),
],
});const AssignedWork = Ontology.QueryType("assigned-work", {
query: {
find: ["?title", "?technician"],
where: [
["?work", ":work-order/title", "?title"],
["?assignment", ":assigned-to/from", "?work"],
["?assignment", ":assigned-to/to", "?person"],
["?person", ":technician/name", "?technician"],
],
},
});
const ontology = defineOntology(
{ name: "field-service", version: "1" },
[Technician, WorkOrder, AssignedTo, AssignWorkOrder, AssignedWork],
);
const runtime = makeOntologyRuntime(ontology);
const input = {
workOrder: "work-order:42",
technician: "technician:ada",
};
const program = Effect.gen(function* () {
yield* runtime.invoke(AssignWorkOrder, input, {
actor: "dispatcher:grace",
commandId: "assign:42",
});
return yield* runtime.query("assigned-work");
});
await Effect.runPromise(
program.pipe(Effect.provide(KvTriples.layer)),
);Palantir showed that software becomes powerful when it speaks the language of the domain. Open Ontology asks how small that idea can get: one focused package, one fact store, and no platform team standing between the model and your application.
Model the nouns your operators recognize—not anonymous tables leaking through an API.
Relationships carry facts. Typed action plans turn intent into one attributed, atomic transaction.
Traverse objects and links with Datalog, inspect prior truth, and know who changed what.
The ontology vocabulary is itself defined in Lisp. Forma loads those form and elaboration programs into portable IR, which can run on Triplex directly or generate the equivalent Effect TypeScript module. One model; two ways to invent it.
(define-entity Technician
(:field [technician/name String {:required true}])
(:field [technician/region String]))
(define-entity WorkOrder
(:field [work-order/title String {:required true}])
(:field [work-order/status String {:required true}])
(:field [work-order/priority Number]))
(define-relation assigned-to WorkOrder Technician
(:field [assigned-to/assigned-at Instant {:required true}]))
(define-action assign-work-order
(:input [workOrder String {:required true}])
(:input [technician String {:required true}])
(:returns String)
(:do (changes
(set workOrder :work-order/status "assigned")
(link assigned-to workOrder technician
{:assigned-to/assigned-at now}))))
(define-datalog-query assigned-work
(:query
{:find ["?title" "?technician"]
:where
[["?work" ":work-order/title" "?title"]
["?assignment" ":assigned-to/from" "?work"]
["?assignment" ":assigned-to/to" "?person"]
["?person" ":technician/name" "?technician"]]}))