A local proof loop. Put the claim in
plan.ts, run it, keep the system honest.
What Gateproof does
Gateproof runs a proof loop: observe the system, act on it, assert the result. If the assertions pass, the gate passes. If not, the loop iterates with a worker until it does — or the budget runs out.
Gateproof owns proof authority. It can delegate the work:
- Built-in worker — executes commands locally
- filepath worker — hands one bounded turn to a filepath instance
- Deja memory — optionally recalls learnings before each iteration and stores new ones after
The observe / act / assert schema is shared with unsurf as proof-spec.v0 — gateproof drives HTTP/exec; unsurf drives the DOM. Specs round-trip between them. See proof-spec.v0 interop below.
Quickstart
bun install bun run example:hello-world
Smallest gate
import { Effect } from "effect"; import { Act, Assert, Gate, Plan, createHttpObserveResource, } from "gateproof"; const scope = { plan: Plan.define({ goals: [ { id: "hello-world", title: "GET / returns hello world", gate: Gate.define({ observe: createHttpObserveResource({ url: "http://localhost:3000/" }), act: [Act.exec("curl -sf http://localhost:3000/")], assert: [ Assert.httpResponse({ status: 200 }), Assert.responseBodyIncludes("hello world"), Assert.noErrors(), ], }), }, ], loop: { maxIterations: 1, stopOnFailure: true }, }), }; const result = await Effect.runPromise(Plan.runLoop(scope.plan)); if (result.status !== "pass") process.exitCode = 1;
Run it
bun run example:hello-world # basic proof loop bun run example:hello-world:worker # with built-in worker bun run example:hello-world:filepath-worker # with filepath worker bun run plan.ts # run the root plan
Worker paths
- Built-in worker — runs commands directly, no external dependencies
- filepath worker — calls
POST /api/workspaces/:id/runon a filepath instance; documented at Use the filepath Worker - Deja memory — attach with
createDejaMemoryRuntime; recalls before each iteration, learns after
Core API
Gate.define({ observe, act, assert }) Plan.define({ goals, loop }) Plan.run(plan) Plan.runLoop(plan) createHttpObserveResource({ url }) createFilepathWorker({ ... }) createDejaMemoryRuntime({ ... }) Act.exec(command) Assert.httpResponse({ status }) Assert.responseBodyIncludes(text) Assert.hasAction(id) Assert.noErrors() Assert.numericDeltaFromEnv(key, threshold) Require.env(key)
proof-spec.v0 interop
Gateproof shares its observe / act / assert schema with unsurf — same loop at different altitudes: gateproof drives HTTP/exec; unsurf drives the DOM. A ProofSpec round-trips between the two.
import { goalToProofSpec, planToProofSpecs, proofSpecToGoal, computeRisk, type ProofSpec, } from "gateproof"; // Gateproof goal → publishable proof-spec (consumed by unsurf's Directory, an MCP client, etc.) const spec = goalToProofSpec(myGoal, { url: "https://example.com/" }); // Proof-spec scouted by unsurf → runnable as a gateproof PlanGoal const goal = proofSpecToGoal(someSpec); // Risk is computed, not claimed. Pure function of the DSL. computeRisk(spec.act); // "low" | "medium" | "high"
Types added: ProofSpec, Observation, DslOp, ProofAssertion, EvidenceBundle, Risk — see src/ProofSpec.ts. Full field reference lives in unsurf's experiments/_proof-spec-v0/SPEC.md.
Reference
- Root plan:
plan.ts - Example:
examples/hello-world/plan.ts - Worker entry:
src/index.ts - proof-spec types + interop:
src/ProofSpec.ts - Run in a loop
- Case studies