LaufwerkLaufwerk

Dataset and Evidence

Contracts, records, revisions, human review binding and composable evidence in alpha.14.

Import from @laufwerk/sdk/evidence. Start with the runnable draft example if these concepts are new. For listing, snapshotting and exporting records, use the dataset guide and CLI reference.

Choose the operation

NeedAPI
Contribute workflow invocations to a datasetworkflow.annotate(Dataset.Definition, Dataset.define({ name, version }))
Describe reusable saved dataEvidence.define({ name, version, kind, schema })
Save a valueEvidence.record({ key, contract, value })
Run an Effect and save its resulteffect.pipe(Evidence.capture({ key, contract }))
Bind one Human response to its candidatehuman.pipe(Evidence.review({ key, subject, contract }))
Record a revised candidateEvidence.record({ key, contract, value, revises })
Call a child with a distinct evidence identitycall(workflow, { key, stage, input })
Give inline workflow code its own evidence scopeeffect.pipe(Evidence.withInvocation(options))
Read a saved record or snapshot in workflow codeEvidenceReader service

Constructors describe data. Recording, capture, review and child calls are Effects that need Laufwerk's runtime. Importing them does not supply those services to an arbitrary Effect.runPromise call.

Dataset definitions

Dataset.define({ name: string, version: number }) returns the definition. Attach it with workflow.annotate(Dataset.Definition, definition). Dataset names must be nonblank and versions positive safe integers when registered. An example is one invocation's input, outcome, records and child stages.

A run is the top-level operating unit. An invocation is one occurrence of work within it. Child workflows can each contribute an example without creating another top-level run or worker.

Contracts and record references

Evidence.define(options) returns a Contract<A, I> with these properties:

PropertyTypeMeaning
namestringNonblank name, such as draft.post
versionnumberPositive safe integer; change it when the persisted contract changes
kindstringNonblank classification; use artifact for candidates and feedback for reviews
schemaSchema.Schema<A, I>Effect schema describing decoded A and encoded I

A contract's name/version identifies an immutable descriptor, including its kind and JSON Schema. Reusing that pair with a different descriptor fails. Other kind strings are allowed; a label alone does not create review or revision semantics.

RecordRef<A> contains recordId: string and an optional type marker. Pass the reference to subject, revises or uses; use its recordId for CLI/HTTP reads. The reference is an identity, not the full saved value.

Evidence.record(options)

PropertyRequiredMeaning
key: stringYesNonblank record identity within the invocation
value: AYesDecoded value to encode and save
contract: Contract<A, I>With no schemaNamed reusable contract
schema: Schema.Schema<A, I>With no contractAd hoc schema; runtime gives it an invocation-specific contract name
revises: RecordRef<A>NoEarlier record this revision replaces; must use the same contract name/version
uses: readonly RecordRef[]NoRecords used as context or evidence

Returns an Effect of RecordRef<A>. Schema encoding and persistence failures use EvidenceError. The store compares the value, contract and links on every call: identical reuse returns the same reference; conflicting reuse fails, including on replay. A new revision needs a new key.

Encoded content must be finite JSON. The serialized stored record content has a 4,000,000-byte UTF-8 ceiling. Store larger files elsewhere and record a stable reference and checksum. Dates need an encoding schema; functions, undefined, cycles and binary values cannot be saved as JSON evidence.

Evidence.capture(options)

Takes key, contract, optional revises and optional uses, with the same meaning as record. Returns an operator for Effect<A, E, R>; its result is { value: A, ref: RecordRef<A> }. Original failures propagate; record failures add EvidenceError.

capture.ts
import { Activity } from "@effect/workflow";
import { Effect, Schema } from "effect";
import { Evidence } from "@laufwerk/sdk/evidence";

const Draft = Evidence.define({
  name: "draft.text", version: 1, kind: "artifact", schema: Schema.String,
});
export const captureDraft = (text: string) => Activity.make({
  name: "compose-draft", success: Schema.String,
  execute: Effect.succeed(text.trim()),
}).pipe(Evidence.capture({ key: "draft/0", contract: Draft }));

Call the helper inside a workflow. Capture does not create a durable boundary for arbitrary external work: keep generation in a Session or Activity. Otherwise replay can produce a different value under the same key and fail the record check.

Evidence.review(options)

Wrap one Human interaction with this operator. It returns that interaction's original answer while attaching the following review binding:

PropertyRequiredMeaning
key: stringYesStable feedback identity
subject: RecordRefYesExact artifact being assessed
contract: Contract<A, I>YesMust use kind: "feedback"; describes the response value
editedArtifactNoBind an approved edited field as described below

For Human.confirm, the feedback schema is Boolean; for text, String; for select, a compatible string/literal schema; for Human.request, the custom form's response schema. It describes the value, not the transport envelope such as { "kind": "confirmation", "value": true }.

Response acceptance and feedback persistence happen in the same SQLite transaction. A new review round needs a new Human key and feedback key. Approval is a recorded judgment; it does not establish factual correctness.

An edited candidate and its approval

editedArtifact accepts:

PropertyTypeMeaning
fieldstringTop-level response field containing the edited artifact
approvedWhen.fieldstringTop-level response field containing the decision
approvedWhen.equalsstring or booleanDecision value that accepts the edit

When the condition matches, the runtime validates the edited value against the original artifact contract, records a revision if it changed, and binds feedback to that exact revision atomically. Other decisions record feedback without creating an approved edit. See custom Human forms.

Child workflows and inline stages

call(workflow, { key, stage, input }) returns the child's success value. The caller key and parent invocation identify the child. Its dataset annotation comes from its workflow definition. Merge the child's registration layer with the parent's layer so it remains available after suspension.

Distinct keys create distinct invocations even for identical inputs. Plain child.execute(input) retains Effect's input-derived identity: cached executions are not fresh trials and retain their original evidence parent.

For inline code, Evidence.withInvocation(options) accepts:

PropertyRequiredMeaning
key: string, stage: stringYesStable occurrence and its role
input: { schema, value }YesEncodable input contract and value
output: Schema.Schema<A, I>YesSchema for the Effect's success
dataset: { name, version }NoDataset this scope contributes to

This operator records an evidence scope, not a new workflow or a durable step. Children share the root run's storage, admission and cancellation boundary. They do not automatically isolate host files or tools. An existing Workspace reference can be passed to share files explicitly.

Human and Session keys are scoped to the invocation; a returned session handle retains its original owner. Keep names distinct for repeated work inside a scope.

Read evidence inside a workflow

Obtain EvidenceReader with yield* EvidenceReader. Its record(recordId) and snapshot(snapshotId) methods return Effects of unknown, failing with EvidenceError. Decode results with the schema your workflow expects.

Example reads include related revisions and feedback even when a parent owns the review, while preserving record provenance. Incoming uses links do not pull in unrelated examples. Snapshots preserve a fixed view; later reviews do not mutate previously saved snapshots.

Next: Save and export datasets · Benchmark reference

On this page