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
| Need | API |
|---|---|
| Contribute workflow invocations to a dataset | workflow.annotate(Dataset.Definition, Dataset.define({ name, version })) |
| Describe reusable saved data | Evidence.define({ name, version, kind, schema }) |
| Save a value | Evidence.record({ key, contract, value }) |
| Run an Effect and save its result | effect.pipe(Evidence.capture({ key, contract })) |
| Bind one Human response to its candidate | human.pipe(Evidence.review({ key, subject, contract })) |
| Record a revised candidate | Evidence.record({ key, contract, value, revises }) |
| Call a child with a distinct evidence identity | call(workflow, { key, stage, input }) |
| Give inline workflow code its own evidence scope | effect.pipe(Evidence.withInvocation(options)) |
| Read a saved record or snapshot in workflow code | EvidenceReader 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:
| Property | Type | Meaning |
|---|---|---|
name | string | Nonblank name, such as draft.post |
version | number | Positive safe integer; change it when the persisted contract changes |
kind | string | Nonblank classification; use artifact for candidates and feedback for reviews |
schema | Schema.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)
| Property | Required | Meaning |
|---|---|---|
key: string | Yes | Nonblank record identity within the invocation |
value: A | Yes | Decoded value to encode and save |
contract: Contract<A, I> | With no schema | Named reusable contract |
schema: Schema.Schema<A, I> | With no contract | Ad hoc schema; runtime gives it an invocation-specific contract name |
revises: RecordRef<A> | No | Earlier record this revision replaces; must use the same contract name/version |
uses: readonly RecordRef[] | No | Records 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.
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:
| Property | Required | Meaning |
|---|---|---|
key: string | Yes | Stable feedback identity |
subject: RecordRef | Yes | Exact artifact being assessed |
contract: Contract<A, I> | Yes | Must use kind: "feedback"; describes the response value |
editedArtifact | No | Bind 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:
| Property | Type | Meaning |
|---|---|---|
field | string | Top-level response field containing the edited artifact |
approvedWhen.field | string | Top-level response field containing the decision |
approvedWhen.equals | string or boolean | Decision 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:
| Property | Required | Meaning |
|---|---|---|
key: string, stage: string | Yes | Stable occurrence and its role |
input: { schema, value } | Yes | Encodable input contract and value |
output: Schema.Schema<A, I> | Yes | Schema for the Effect's success |
dataset: { name, version } | No | Dataset 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.