Adding HTTP Endpoints to an Effect Golem Agent
SkillCloud & infraLets your agent add HTTP endpoints and REST routes to an Effect-based Golem agent.
Use Adding HTTP Endpoints to an Effect Golem Agent in Claude, ChatGPT or Ahel Desktop
Free. Sign in, add Adding HTTP Endpoints to an Effect Golem Agent and connect your AI. About a minute.
Also: Claude Code · Cursor · Codex
Then ask your AI: use the Adding HTTP Endpoints to an Effect Golem Agent skill
Details
Instructions available. Your AI can read the instructions. Execution depends on the setup they require.
Account requirements not reviewed. Check the skill instructions before use; ahel provides instructions and does not run this skill.
No other account needed.
Add ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.
About this skill
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.
Durable Stream Route Customization
For a method with stream inputs or outputs, pass durableStreams in the Http endpoint options, or pipe the endpoint through Http.withDurableStreams(...):
http: [Http.post("/events", {
durableStreams: {
slots: [
{ source: "input", slot: "input", name: "uploads" },
{
source: "output",
slot: "$result",
name: "events",
contentType: "application/vnd.example.events",
},
],
allowExternalWrites: true,
allowStreamDelete: false,
allowInvocationDelete: false,
load: {
maxConcurrentReadersPerStream: 8,
maxAppendRequestsPerSecondPerStream: 25,
},
},
})],
sourceandslotselect canonical top-level stream slots.$resultis the only implicit result selector.namechanges the public URL and OpenAPI name; the canonical name is no longer accepted in the public URL.contentTypeis allowed only for a direct byte stream (stream<u8>) and must be a concrete non-text, non-JSON MIME type without parameters or wildcards. JSON-shaped streams stayapplication/json; do not usetext/plainforstream<string>. SSE still usestext/event-streamand base64 data.- The three
allow*options default totrue. Setting one tofalseremoves that protocol operation and produces405with an exactAllowheader. - The live-reader limit is 1 through 16 and applies to long-poll and SSE. The append limit must be positive and cannot be set when external writes are disabled. A route-local limit rejection is
429withRetry-After: 1. - Limits are maintained per route and worker-service node, not as a cluster-wide quota.
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
- 209
- Last commit
- Oct 2026
Advanced
- Item type
- skill
- Key
golem-add-http-endpoint-effect- Source
- github.com/golemcloud/golem
Related picks
Skill · mattpocock
The pick for TypeScripttypescript-pro
Skill · jeffallan
The pick for TypeScriptfind-skills
Skill · vercel-labs
More in Cloud & infravercel-react-best-practices
Skill · vercel-labs
More in Cloud & infraturborepo
Skill · vercel
More in Cloud & inframicrosoft-foundry
Skill · microsoft
More in Cloud & infra