@zkops/keel
v1.0.0
Published
Privacy-preserving AI pipeline SDK for Midnight Network
Maintainers
Readme
keel
Privacy-preserving AI pipeline SDK for Midnight Network.
@zkops/keel provides a composable pipeline abstraction for building verifiable, privacy-preserving workflows on Midnight. It bridges AI inference, zero-knowledge proof generation, and on-chain execution — giving developers a typed, observable interface for orchestrating multi-step workflows where computation is private, outputs are verifiable, and audit trails are cryptographically anchored.
Installation
npm install @zkops/keelPeer dependencies are optional and only required when using the corresponding sub-modules:
npm install ollama # for @zkops/keel/witness with Ollama
npm install @anthropic-ai/sdk # for @zkops/keel/witness with Anthropic
npm install openai # for @zkops/keel/witness with OpenAIModules
The package exposes three entry points:
| Import path | Description |
|---|---|
| @zkops/keel | Core pipeline engine |
| @zkops/keel/witness | AI model as a private ZK witness input |
| @zkops/keel/prov | PROV-O provenance tracing with on-chain anchoring |
Core Pipeline
Build typed, observable pipelines from composable stages. Each stage is named and emits structured lifecycle events.
import { Pipeline, localStage, zkStage, anchorStage } from '@zkops/keel'
const pipeline = new Pipeline()
.addStage(localStage('validate', async (input) => validate(input)))
.addStage(zkStage('prove', async (input) => generateProof(input)))
.addStage(anchorStage('commit', async (proof) => anchor(proof)))
const result = await pipeline.run(myInput, {
onEvent: (e) => console.log(`[${e.kind}] ${e.stageName} ${e.durationMs}ms`)
})Stage kinds
| Factory | Kind | Use when |
|---|---|---|
| localStage | local | Off-chain computation, data preparation |
| zkStage | zk | ZK proof generation, Midnight contract interaction |
| anchorStage | anchor | On-chain commitment, ledger writes |
| branchStage | branch | Conditional routing to different sub-stages |
| conditionalStage | conditional | Optional stage execution based on runtime state |
| parallelStage | parallel | Concurrent independent stage execution |
Branching and Conditional Logic
branchStage
Routes execution to a different stage based on the result of a developer-supplied condition function. The condition can return any string, boolean, or number — used as the key to select the matching branch. The branch decision is recorded in the event stream and provenance graph.
import { Pipeline, branchStage, localStage } from '@zkops/keel'
const pipeline = new Pipeline()
.addStage(
branchStage('classify-product', {
condition: (product) => product.category,
branches: {
pharmaceutical: pharmComplianceStage,
textile: textileComplianceStage,
food: foodSafetyStage,
},
fallback: defaultComplianceStage,
})
)The fallback stage is optional. If no matching branch is found and no fallback is configured, the stage throws with a descriptive error.
conditionalStage
Runs a stage only when a condition returns true. When the condition returns false, the input passes through unchanged unless a fallback function is provided.
import { Pipeline, conditionalStage } from '@zkops/keel'
const pipeline = new Pipeline()
.addStage(
conditionalStage('high-value-review', {
condition: (tx) => tx.value > 10_000,
stage: aiReviewStage,
fallback: async (tx) => ({ ...tx, reviewed: false }),
})
)parallelStage
Runs multiple independent stages concurrently against the same input. Results are collected and passed to a developer-supplied merge function. If any stage throws, the parallel stage throws immediately.
import { Pipeline, parallelStage, localStage } from '@zkops/keel'
const pipeline = new Pipeline()
.addStage(
parallelStage('compliance-checks', {
stages: [
localStage('origin-check', runOriginCheck),
localStage('quality-check', runQualityCheck),
localStage('sanctions-check', runSanctionsCheck),
],
merge: (results) => ({
passed: results.every((r) => r.passed),
checks: results,
}),
})
)AI Witness
Use AI model outputs as private inputs to ZK circuits. The model call runs locally or remotely via a provider-agnostic interface. The prompt, model output, and reasoning never reach the chain. A cryptographic receipt — containing hashed prompt, hashed output, model identity, and timestamp — is produced alongside the structured output.
import { Pipeline, zkStage } from '@zkops/keel'
import { aiWitnessStage, ollamaWitness } from '@zkops/keel/witness'
import { z } from 'zod'
const witness = ollamaWitness({
model: 'llama3.2',
baseUrl: 'http://localhost:11434',
})
const DecisionSchema = z.object({
approved: z.boolean(),
confidence: z.number().min(0).max(100),
reason: z.string(),
})
const pipeline = new Pipeline()
.addStage(
aiWitnessStage('ai-guard', witness, {
prompt: (input) => `Evaluate this operation: ${JSON.stringify(input)}`,
schema: DecisionSchema,
map: (result, input) => {
if (!result.output.approved) {
throw new Error(`Rejected: ${result.output.reason}`)
}
return { ...input, receipt: result.receipt }
},
})
)
.addStage(
zkStage('execute', async ({ input, receipt }) => submitToChain(input, receipt))
)Supported providers
| Provider | Factory | Requires |
|---|---|---|
| Ollama (local) | ollamaWitness | ollama |
| Anthropic | anthropicWitness | @anthropic-ai/sdk |
| OpenAI | openAIWitness | openai |
All providers implement the same AIWitnessProvider interface. Custom providers can be supplied by implementing that interface directly.
Provenance Tracing
Attach a provenance tracer to any pipeline to automatically record every stage execution as a PROV-O compliant activity. The resulting graph is content-addressed and can be anchored on-chain via any anchor provider.
import { Pipeline, localStage } from '@zkops/keel'
import { ProvenanceTracer, consoleAnchor } from '@zkops/keel/prov'
const tracer = new ProvenanceTracer({
agent: {
id: 'my-service',
type: 'zkops:pipeline',
version: '1.0.0',
},
anchor: consoleAnchor(),
})
const pipeline = new Pipeline()
.addStage(localStage('process', async (input) => process(input)))
tracer.attach(pipeline)
await pipeline.run(myInput)
await tracer.anchor()
const graph = tracer.getGraph()
console.log(graph.rootHash) // deterministic hash of the full execution
console.log(graph.activities) // PROV-O activities, one per stageAnchor providers
| Factory | Behaviour |
|---|---|
| noopAnchor() | Returns the hash without side effects. Suitable for testing. |
| consoleAnchor() | Logs the hash to stdout. Suitable for development. |
| customAnchor(fn) | Calls your function with the root hash. Wire up any ledger. |
Supply a customAnchor to write the hash to a Midnight contract, IPFS, or any other persistence layer:
import { customAnchor } from '@zkops/keel/prov'
const anchor = customAnchor(async (rootHash) => {
const tx = await midnightClient.submitAnchor(rootHash)
return {
rootHash,
anchoredAt: Date.now(),
transactionId: tx.id,
blockHeight: tx.blockHeight,
}
})Events
Every stage emits structured events to the onEvent callback and to any attached ProvenanceTracer. Events are typed as a discriminated union.
type PipelineEvent =
| PipelineEventBase // kind: 'start' | 'complete' | 'error'
| BranchEvent // kind: 'branch' — includes branchTaken and conditionKey
| ParallelEvent // kind: 'parallel' — includes stageNamesawait pipeline.run(input, {
onEvent: (e) => {
if (e.kind === 'branch') console.log(`branch: ${e.branchTaken} (key: ${e.conditionKey})`)
if (e.kind === 'parallel') console.log(`parallel: ${e.stageNames.join(', ')}`)
if (e.kind === 'error') console.error(`error in ${e.stageName}:`, e.error)
}
})Repository Structure
packages/
core/ internal — pipeline engine, stage primitives, types
witness/ internal — AI witness providers and stage factory
prov/ internal — PROV-O provenance tracer and anchor providers
keel/ published — @zkops/keel umbrella package
examples/
counter/ Midnight counter contract wrapped as a keel pipelineThe three internal packages are not published to npm. All public API is exported through @zkops/keel.
Development
Prerequisites
- Node.js 22+
- pnpm 9+
Setup
git clone https://github.com/zkops/keel.git
cd keel
pnpm install
pnpm build
pnpm testCommands
| Command | Description |
|---|---|
| pnpm build | Build all packages via Turborepo |
| pnpm test | Run all test suites with coverage |
| pnpm lint | Run ESLint across all packages |
| pnpm typecheck | Run TypeScript type checking |
| pnpm release | Build and publish to npm via Changesets |
Contributing
Contributions are welcome. Please open an issue before submitting a pull request for significant changes. All pull requests require passing tests, lint, and type checks.
License
Apache-2.0. See LICENSE for details.
Portions of the counter example are based on the Midnight counter example by Midnight Foundation, also Apache-2.0. See examples/counter/NOTICE.
