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 that should fail and
retry the invocation. 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/.