Skip to Content
How-To GuidesEffectAdding an Agent to an Effect Golem Component

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

  1. Add src/<agent-name>.ts with the schemas, definition, and implementation.
  2. Declare constructor identity and method contracts with defineAgent and method.
  3. Implement init as an Effect and derive all handlers from its shared state with methods.
  4. For snapshot-enabled Ref state, declare Snapshot.define(...) and use snapshot: Snapshot.ref<Saved>().
  5. Add import "./<agent-name>.js" to src/main.ts so the implementation registers.
  6. Run golem build to 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 effect and 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 Effect values; do not implement them as plain async functions.
  • Use Snapshot.ref<Saved>() when a snapshotted Ref contains 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.ts for side-effect registration.
  • Do not edit files under golem-temp/.