Adding HTTP Endpoints to an Effect Golem Agent
SkillCloud & infraGuides your agent through exposing an Effect-based Golem agent as HTTP endpoints with routes and parameter bindings.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Adding HTTP Endpoints to an Effect Golem Agent skill
About this capability
Exposing an Effect-based Golem agent over HTTP. Use when adding HTTP mounts, REST endpoints, request parameter bindings, or an httpApi deployment to an @golemcloud/effect-golem agent.
What this skill tells your AI
The instructions your AI receives, as published by golemcloud/golem in golem-skills/skills/effect/golem-add-http-endpoint-effect/SKILL.md and read by ahel’s review.
Effect agents publish HTTP route metadata with the Http namespace from
@golemcloud/effect-golem. The Golem host serves the routes, decodes path, query, header, and JSON
body values into method parameters, invokes the agent, and maps the method result to an HTTP
response.
Steps
- Define the agent and its methods with
defineAgent,method, and Effect Schema. - Add
http: Http.mount(...)to the agent definition. - Add an
http: [Http.get(...) | Http.post(...) | ...]array to every exposed method. - Implement every handler as an
Effectand import its module fromsrc/main.ts. - Add the agent type to an
httpApidomain deployment ingolem.yaml. - Run
golem build, then deploy withgolem deploy --yes.
Related Skills
| Skill | When to Load |
|---|---|
golem-add-agent-effect | Defining the Effect agent, method schemas, and durable state |
golem-http-params-effect | Detailed path, query, header, body, and response mapping |
golem-make-http-request-effect | Making outgoing HTTP requests from an Effect handler |
golem-add-http-auth-effect | Enabling authentication and reading the caller principal |
golem-add-cors-effect | Configuring mount-level or endpoint-level CORS |
golem-configure-api-domain | Configuring the httpApi deployment domain |
Mount Path
Import Http from the Effect Golem SDK and put one mount on the agent definition:
import { Effect, Schema } from "effect";
import { defineAgent, Http, method } from "@golemcloud/effect-golem";
export const TaskAgent = defineAgent({
name: "TaskAgent",
mode: "durable",
id: {
taskName: Schema.String,
},
http: Http.mount("/api/tasks/{taskName}"),
methods: {
// ...
},
}).implement({
init: () => Effect.void,
methods: () => ({
// ...
}),
});
Mount rules:
- The path starts with
/and does not end with/unless it is exactly/. - Every agent id field appears as a
{variable}in the mount path. - Variable names use the exact TypeScript
idkeys, including casing. - Mount paths cannot contain query parameters or
{*rest}catch-all variables. - Use
{taskName}, not{task-name}, for a constructor field namedtaskName. This changes only the placeholder name; both forms would match the same concrete URL segment.
For a fresh phantom agent instance per HTTP request, set the mount option rather than changing the agent mode:
http: Http.mount("/gateway/{name}", { phantomAgent: true }),
mode still accepts only "durable" or "ephemeral"; it is independent of
phantomAgent.
Endpoint Declarations
Declare routes on the corresponding method. Endpoint paths are relative to the mount:
methods: {
listItems: method({
input: {},
success: Schema.Array(Item),
http: [Http.get("/items")],
}),
createItem: method({
input: {
name: Schema.String,
count: Schema.Number,
},
success: Item,
http: [Http.post("/items")],
}),
updateItem: method({
input: {
id: Schema.String,
name: Schema.String,
},
success: Schema.NullOr(Item),
http: [Http.put("/items/{id}")],
}),
deleteItem: method({
input: { id: Schema.String },
success: Schema.Void,
http: [Http.del("/items/{id}")],
}),
},
Use these exact helpers:
| HTTP route | Effect Golem helper |
|---|---|
| GET | Http.get(path, options?) |
| POST | Http.post(path, options?) |
| PUT | Http.put(path, options?) |
| DELETE | Http.del(path, options?) |
| Custom method | Http.custom("METHOD", path, options?) |
One method can have multiple routes by adding multiple entries to its http array.
Parameter Mapping
Bindings always refer to exact method input keys:
searchItems: method({
input: {
category: Schema.String,
query: Schema.String,
minPrice: Schema.NullOr(Schema.Number),
tenant: Schema.String,
},
success: Schema.Array(Item),
http: [
Http.get(
"/categories/{category}/search?q={query}&min-price={minPrice}",
{ headers: { "X-Tenant": "tenant" } as const },
),
],
}),
{category}binds a path segment toparams.category.q={query}binds query keyqtoparams.query; the URL key and TypeScript variable may have different names.- Missing optional query or header values decode to
nullwhen declared withSchema.NullOr(...). headersmaps HTTP header names to method parameter names.- A parameter can be bound from only one of path, query, or headers in a given endpoint.
Http.gethas no body, so every parameter must be bound explicitly.- For
post,put,del, and other bodyful endpoints, every unbound parameter comes from a JSON object field with the same TypeScript name. For example, unboundinStockexpects{ "inStock": true }.
Endpoint paths start with /. A {*rest} catch-all is allowed only as the final endpoint path
segment.
HTTP Response Mapping
The Golem HTTP host maps Effect method result schemas as follows:
| Method success schema | Handler success value | HTTP response |
|---|---|---|
Schema.Void | Effect.void | 204, empty body |
T | Effect<T> | 200, JSON T |
UnstructuredText(...) | inline text reference | 200, plain text |
UnstructuredBinary(...) | inline binary reference | 200, raw bytes |
Schema.NullOr(T) | T | 200, JSON T |
Schema.NullOr(T) | null | 404, empty body |
method with error: E | Effect.fail(E) | 500, JSON E |
Schema.NullOr(T) is lowered by the Effect SDK to WIT option<T>, which is what enables the
host's 200/404 mapping. Use a declared error schema and Effect.fail(...) for expected typed
failures; do not use defects for normal not-found behavior.
Use an unstructured success value when the response itself must be plain text or binary rather than JSON:
import { Unstructured } from "@golemcloud/effect-golem";
const textSuccess = Unstructured.UnstructuredText({
restrictions: [{ languageCode: "en" }],
});
const binarySuccess = Unstructured.UnstructuredBinary({
restrictions: [{ mimeType: "application/octet-stream" }],
});
const textValue = {
_tag: "inline" as const,
val: {
data: "hello from Effect",
textType: { languageCode: "en" },
},
} satisfies Unstructured.TextReferenceValue;
const binaryValue = {
_tag: "inline" as const,
val: {
data: new Uint8Array([0, 127, 255]),
binaryType: { mimeType: "application/octet-stream" },
},
} satisfies Unstructured.BinaryReferenceValue;
Put textSuccess or binarySuccess in the method's success field and return
Effect.succeed(textValue) or Effect.succeed(binaryValue) from its handler. Inline text maps to a
text/plain body and uses textType.languageCode as Content-Language; inline binary maps to the
declared binaryType.mimeType. Unstructured successes cannot also declare a typed method error.
Use Schema.Struct(...), arrays, and other ordinary schemas for JSON responses, and Schema.Void
for a 204 empty response. These are fixed host mappings, not an arbitrary response builder.
Complete Durable Example
import { Effect, Ref, Schema } from "effect";
import { defineAgent, Http, method, Snapshot } from "@golemcloud/effect-golem";
const TodoItem = Schema.Struct({
id: Schema.String,
title: Schema.String,
done: Schema.Boolean,
});
const TodoState = Schema.Struct({
items: Schema.Array(TodoItem),
});
export const TodoAgent = defineAgent({
name: "TodoAgent",
mode: "durable",
id: {
listName: Schema.String,
},
http: Http.mount("/todos/{listName}"),
snapshotting: Snapshot.define({
schema: TodoState,
policy: Snapshot.policy.everyN(10),
}),
methods: {
createItem: method({
input: { title: Schema.String },
success: TodoItem,
http: [Http.post("/items")],
}),
listItems: method({
input: {},
success: Schema.Array(TodoItem),
http: [Http.get("/items")],
}),
completeItem: method({
input: { id: Schema.String },
success: Schema.NullOr(TodoItem),
http: [Http.post("/items/{id}/complete")],
}),
},
}).implement({
init: () => Ref.make({ items: [] as ReadonlyArray<typeof TodoItem.Type> }),
methods: (state) => ({
createItem: ({ title }) =>
Ref.modify(state, ({ items }) => {
const item = {
id: String(items.length + 1),
title,
done: false,
};
return [item, { items: [...items, item] }] as const;
}),
listItems: () => Ref.get(state).pipe(Effect.map(({ items }) => items)),
completeItem: ({ id }) =>
Ref.modify(state, ({ items }) => {
const existing = items.find((item) => item.id === id);
if (existing === undefined) return [null, { items }] as const;
const updated = { ...existing, done: true };
return [
updated,
{
items: items.map((item) => (item.id === id ? updated : item)),
},
] as const;
}),
}),
snapshot: Snapshot.ref<{ items: ReadonlyArray<typeof TodoItem.Type> }>(),
});
Register the top-level implementation:
// src/main.ts
import "./todo-agent.js";
Local imports use the emitted .js suffix in generated ESM projects.
Domain Deployment
Add the agent to the existing httpApi deployment without removing other agents:
httpApi:
deployments:
local:
- domain: my-app.localhost:9006
agents:
TodoAgent: {}
Current Golem 1.5 manifests contain deployment configuration only. Do not add legacy
apiDefinitions, route lists, OpenAPI extension bindings, or Rib response scripts. Route metadata
comes from Http.mount(...) and the method-level Http.*(...) declarations. Golem serves the
generated OpenAPI document at /openapi.yaml after deployment.
Key Constraints
- Import Effect APIs from
effectanddefineAgent,Http,method, andSnapshotfrom@golemcloud/effect-golem. - Do not use decorators or classes from
@golemcloud/golem-ts-sdkin an Effect component. - Every agent id field must appear in the mount path with exact TypeScript casing.
- Every bound path, query, or header variable must match a method parameter.
- Unbound bodyful-method parameters use same-named camelCase JSON body fields.
- Handlers return Effects, not plain values or
asyncfunctions. - Use
Snapshot.ref<Saved>()when a snapshottedRefcontains the saved schema value. - Import the implementation module from
src/main.ts; otherwise it is not registered. - Do not edit generated files under
golem-temp/.
Signals
- GitHub stars
- 2k
- Forks
- 211
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
golem-add-http-endpoint-effect- Source
- github.com/golemcloud/golem