Adding an Agent to an Effect Golem Component
Effect Golem agents declare their public contract with Effect Schema and implement every method
as an Effect. A top-level defineAgent(...).implement({ init, methods }) call registers the agent when its
module is imported.
Steps
- Add
src/<agent-name>.tswith the schemas, definition, and implementation. - Declare constructor identity and method contracts with
defineAgentandmethod. - Implement
initas an Effect and derive all handlers from its shared state withmethods. - For snapshot-enabled
Refstate, declareSnapshot.define(...)and usesnapshot: Snapshot.ref<Saved>(). - Add
import "./<agent-name>.js"tosrc/main.tsso the implementation registers. - Run
golem buildto type-check and build the component.
Durable Agent Example
import { Effect, Ref, Schema } from "effect";
import { defineAgent, method, Snapshot } from "@golemcloud/effect-golem";
const Item = Schema.Struct({
id: Schema.String,
name: Schema.String,
});
const RepositoryState = Schema.Struct({
items: Schema.Record(Schema.String, Item),
});
export const ItemRepositoryAgent = defineAgent({
name: "ItemRepositoryAgent",
mode: "durable",
id: {
repositoryName: Schema.String,
},
snapshotting: Snapshot.define({
schema: RepositoryState,
policy: Snapshot.policy.everyN(10),
}),
methods: {
createItem: method({
input: { item: Item },
success: Item,
}),
getItem: method({
input: { id: Schema.String },
success: Item,
}),
updateItem: method({
input: { item: Item },
success: Item,
}),
deleteItem: method({
input: { id: Schema.String },
success: Schema.Boolean,
}),
listItems: method({
input: {},
success: Schema.Array(Item),
}),
},
}).implement({
init: () => Ref.make({ items: {} as Record<string, typeof Item.Type> }),
methods: (state) => ({
createItem: ({ item }) =>
Ref.update(state, ({ items }) => ({
items: { ...items, [item.id]: item },
})).pipe(Effect.as(item)),
getItem: ({ id }) =>
Ref.get(state).pipe(
Effect.map(({ items }) => items[id]),
Effect.flatMap((item) =>
item === undefined
? Effect.die(new Error(`item not found: ${id}`))
: Effect.succeed(item),
),
),
updateItem: ({ item }) =>
Ref.update(state, ({ items }) => ({
items: { ...items, [item.id]: item },
})).pipe(Effect.as(item)),
deleteItem: ({ id }) =>
Ref.modify(state, ({ items }) => {
if (!(id in items)) return [false, { items }] as const;
const { [id]: _, ...remainingItems } = items;
return [true, { items: remainingItems }] as const;
}),
listItems: () =>
Ref.get(state).pipe(Effect.map(({ items }) => Object.values(items))),
}),
snapshot: Snapshot.ref<{ items: Record<string, typeof Item.Type> }>(),
});Register the implementation from the component entry point:
// src/main.ts
import "./item-repository-agent.js";Local imports use the emitted .js suffix because generated Effect projects use ESM and
NodeNext module resolution.
Method Contracts and Errors
input is a record of named method parameters. A method with one record parameter declares the
record schema as that parameter’s value:
createItem: method({
input: { item: Item },
success: Item,
});Expected domain failures belong in the Effect error channel and need a matching error schema:
const ItemNotFound = Schema.Struct({
_tag: Schema.Literal("ItemNotFound"),
id: Schema.String,
});
getItem: method({
input: { id: Schema.String },
success: Item,
error: ItemNotFound,
});
// In the implementation:
Effect.fail({ _tag: "ItemNotFound" as const, id });Use defects such as Effect.dieMessage(...) only for unexpected failures. An unhandled defect
fails the invocation without retry and does not repeat completed effects. Do not use defects to
represent normal business outcomes.
Key Constraints
- Import Effect APIs from
effectand Golem APIs from@golemcloud/effect-golem. - Use Effect Schema values for every agent id field, method parameter, success, and typed error.
- Constructor parameters define durable agent identity.
- Handlers return
Effectvalues; do not implement them as plainasyncfunctions. - Use
Snapshot.ref<Saved>()when a snapshottedRefcontains the saved schema value. - Keep snapshot values schema-serializable; do not put JavaScript
Map, functions, or services in snapshot state. - Agents are created on first invocation and process invocations sequentially.
- Import every implementation module from
src/main.tsfor side-effect registration. - Do not edit files under
golem-temp/.