Behavior as data. A declarative logic layer built on Effect v4 and its first-class Schema module.
schema-logic is the second floor of a larger stack:
Schema structure as data ← Effect Schema (given)
Logic behavior as data ← this repo
Workflow process as data ← later
Ontology system as data ← later
Most application logic is written as opaque functions:
function approveCandidate(ctx) {
if (ctx.role !== "reviewer") throw new Error("forbidden")
ctx.candidate.status = "approved"
emit("candidate.approved", { id: ctx.candidate.id })
}An agent — or a devtool, or an auditor — can only run that. It cannot read it.
schema-logic represents the same behavior as an inspectable value:
const ApproveCandidate = Logic.Action("ApproveCandidate", ($) => $.do(
$.require($.eq($.ref.actor.role, "reviewer"), "forbidden"),
$.set($.ref.candidate.status, "approved"),
$.emit("candidate.approved", { id: $.ref.candidate.id }),
))This is a value: an AST you can execute, diff, trace, serialize, explain, or hand to an agent to reason about — not just call.
The bet, shared with projects like Foldkit, Datomic, and Palantir Foundry, is that correctness and machine-legibility come from constraining the shape of programs, not from maximizing developer freedom. When every state transition flows through one inspectable form, you get the same payoff Foldkit's Model → Message → Update loop gets for UI — replay, time-travel, semantic diffs, execution traces — but for all application logic, not just rendering.
schema-logic is the "Logic as a value" layer that Datomic (facts as data) and Foundry (organizations as data) gestured at but never quite reified.
v0 is intentionally narrow. It ships the Logic layer and nothing above it. Workflow and Ontology are out of scope until the Logic primitives and interpreter are solid.
- AST construction via a namespaced builder API (
Logic.Action,$.do,$.set, …) with proxy-based symbolic refs ($.ref.candidate.status). - An Effect-based interpreter —
Logic.evaluate(action, input)returnsEffect<Result, LogicError, Store | EventSink>, so reads, writes, and events run through injectable services and inherit Effect's error channel, concurrency, and tracing. - A single pnpm package on
effect@4(Schema is a first-class export of v4), tested withvitest.
| Group | Primitives |
|---|---|
| Kernel | do, let, if, eq, and, or, not, literals, refs |
| Core mutation | require, set, get / query, create, emit |
| Collections | map, filter, match |
| Control / failure | fail, return, retry |
No Level-3 domain verbs (e.g. approve, assign) in v0 — those are meant to be composed from the kernel, and proving they can be is the point of this milestone.
- Runtime/data:
effect-smol(Effect v4, currently4.0.0-beta.74), vendored as a git submodule at.context/effect-v4for reference. Schema is imported directly:import { Schema, Effect } from "effect". - Package manager: pnpm
- Tests: vitest
git clone --recurse-submodules <this-repo>
# or, if already cloned:
git submodule update --init --depth 1See PLAN.md for the build plan.