Types and schemas
How to define workflow input, activity results and structured agent output, with checked schema examples.
Laufwerk uses Effect Schema for runtime validation and persistence. A TypeScript
interface alone describes what the compiler expects; a schema also checks a value
received from JSON, a human, a service or an agent. Import Schema from effect.
Which schema goes where?
| Argument | Accepted schema | Purpose |
|---|---|---|
Workflow.make({ payload }) | Struct fields or compatible struct schema | Decode workflow input |
Workflow / Activity success | Effect Schema | Record the returned result |
Workflow / Activity error | Effect Schema | Record typed failures |
Session output | Schema.Schema<A, I, never> | Generate JSON guidance and decode agent output |
AI SDK tool inputSchema | AI SDK flexible schema, such as Zod | Describe and validate tool arguments |
The following are common Effect Schema building blocks, not an exhaustive reference for the entire Effect library.
| Expression | Value it describes |
|---|---|
Schema.String, Schema.Number, Schema.Boolean | Primitive values |
Schema.Literal("approve", "revise") | Exactly one of these string literals |
Schema.Struct({ title: Schema.String }) | Object with required title |
Schema.Array(Schema.String) | Array of strings |
Schema.optional(Schema.String) | Optional property in a struct |
Schema.NullOr(Schema.String) | String or explicit null |
Schema.Union(A, B) | A value matching either schema |
Schema.Void | No result value; default workflow/activity success |
Schema.Never | No typed failure value; default workflow/activity error |
Optional and nullable are different. A required nullable field must still be
present; an optional string can be omitted but does not accept null.
Example: a review result
import { Schema } from "effect";
export const Review = Schema.Struct({
decision: Schema.Literal("approve", "revise"),
summary: Schema.String,
findings: Schema.Array(Schema.Struct({
file: Schema.String,
explanation: Schema.String,
})),
nextStep: Schema.optional(Schema.String),
});
export type ReviewResult = typeof Review.Type;
export const example: ReviewResult = {
decision: "revise",
summary: "An empty input produces an incorrect total.",
findings: [{ file: "total.ts", explanation: "Return zero for an empty list." }],
};Pass Review as a session's output; the successful result has the inferred
ReviewResult type. Valid JSON matching this shape is still not proof that the
review's claims are correct.
Encoded and decoded values
Schema.Schema<A, I, R> describes decoded value A, encoded representation I,
and any required services R. typeof SomeSchema.Type extracts A;
typeof SomeSchema.Encoded extracts I.
For ordinary structs these often match. A transform can make them different:
Schema.NumberFromString accepts a numeric string and decodes it to a number.
import { expect, test } from "bun:test";
import { Schema } from "effect";
test("decode, optional and nullable fields have distinct contracts", () => {
expect(Schema.decodeUnknownSync(Schema.NumberFromString)("42")).toBe(42);
const Input = Schema.Struct({
note: Schema.optional(Schema.String),
reason: Schema.NullOr(Schema.String),
});
expect(Schema.decodeUnknownSync(Input)({ reason: null })).toEqual({ reason: null });
expect(() => Schema.decodeUnknownSync(Input)({ reason: null, note: null })).toThrow();
expect(() => Schema.decodeUnknownSync(Input)({})).toThrow();
});Session output schemas must require no services (R = never) and support JSON
Schema generation. Arbitrary runtime values such as a live process, function or
execution provider cannot be sent back by the agent as JSON. Prefer a plain data
schema for a result you intend to record.
Error types
The root SDK exports SessionError, WorkspaceError and HumanError. Each is
an Error subclass with a matching name, a message, and an optional cause.
Construct them using the standard new ErrorType(message, { cause }) shape.
An Effect's typed failure and a defect are different. SDK operations fail in the
typed error channel; unexpected thrown errors can become defects. If your workflow
declares error: Schema.String, map its typed errors with
Effect.mapError(error => String(error)). This does not catch defects or make
external side effects safe to retry.