Exposing a Go Agent over HTTP
Overview
HTTP mounting is metadata only: you declare, on the agent’s definition, a URL prefix for the agent and a route for each method. The platform routes matching requests to the right instance and method — there is no incoming-request handler to write. To handle raw requests yourself (a net/http handler, streaming bodies, a website), use an HTTP router instead (see golem-http-router-go).
Two pieces, both on the definition:
Spec.HTTP = &golem.Mount{Path: "…"}— the agent-level prefix. Its{var}segments bind the agent’s constructor (ID) fields, so a request URL selects the instance. EveryIDfield must appear as a{var}.golem.HTTP(golem.GET("/suffix"), …)as a method option — one or more routes per method, with request data bound to the method’s input fields.
Steps
- Add a mount to the agent’s
Spec:HTTP: &golem.Mount{Path: "/counters/{name}"}. - Add routes to methods with the
golem.HTTP(...)option and a verb constructor. - Bind request data to input fields (path
{var},golem.Query,golem.Header, or the JSON body). - Build, then deploy behind an HTTP API so the routes are served.
Example (definition)
package counter
import "github.com/golemcloud/golem/sdks/go/golem"
type ID struct{ Name string } // bound by {name} in the mount path
type AddIn struct{ By int64 }
var Agent = golem.DefineAgent[ID](golem.Spec{
Name: "CounterAgent",
Description: "A counter exposed over HTTP",
// {name} binds ID.Name; CORS/Auth optional.
HTTP: &golem.Mount{Path: "/counters/{name}", CORS: []string{"*"}},
})
var (
// GET /counters/{name}/value — options compose: Desc + HTTP in one call.
Value = Agent.Method[golem.Unit, int64]("value", golem.Description("Read the current value"), golem.HTTP(golem.GET("/value")))
// POST /counters/{name}/add — AddIn arrives as the JSON body
Add = Agent.Method[AddIn, int64]("add", golem.Description("Add to the count"), golem.HTTP(golem.POST("/add")))
)The handlers are ordinary handlers (see golem-add-agent-go) — nothing HTTP-specific in the impl.
Binding request data to input fields
- Path variables —
{sku}in a route suffix binds the input fieldSKU:golem.GET("/items/{sku}"). - Query parameters —
golem.Query("detailed", "detailed")binds?detailed=to theDetailedfield:Lookup = Agent.Method[LookupIn, ItemInfo]("lookup", golem.HTTP(golem.GET("/items/{sku}", golem.Query("detailed", "detailed")))) - Headers —
golem.Header("X-Tenant", "tenant")binds the header to theTenantfield. - Body — for body-carrying verbs (
POST/PUT), input fields not bound to path/query/header come from the JSON request body.
Auth & CORS
Mount{Auth: true}requires auth on every endpoint;golem.EndpointAuth(true|false)overrides it per route:golem.HTTP(golem.GET("/items/{sku}", golem.EndpointAuth(true)))Mount{CORS: []string{"*"}}sets allowed origins for the agent;golem.EndpointCORS(patterns...)adds patterns for one route.
Verbs
golem.GET, golem.POST, golem.PUT, golem.DELETE, and golem.Custom("VERB", "/path"). GET/HEAD are bodyless — bind their inputs from path/query/header.
Durable Stream Route Customization
A method that takes or returns golem.AgentStreams is served as a Durable Streams URL family. golem.DurableStreams customizes it:
type UploadIn struct {
Chunks golem.AgentStream[byte]
File string
}
Upload = Agent.Method[UploadIn, int64]("upload",
golem.HTTP(golem.POST("/upload?file={file}", golem.DurableStreams(golem.StreamRoute{
Slots: []golem.StreamSlot{
{Input: "chunks", Name: "data", ContentType: "application/vnd.example.media"},
{Result: true, Name: "size"},
},
NoStreamDelete: true,
NoInvocationDelete: true,
MaxReadersPerStream: 8,
}))))- A slot is selected by exactly one of
Input(anAgentStreaminput field, by the same schema namegolem.Query/golem.Headerbind),Output(anAgentStreamfield of the returned struct) orResult: true(the returned value itself). Namechanges the public URL and OpenAPI name; the canonical name is no longer accepted.ContentTypeis allowed only for a directAgentStream[byte]slot and must be a concrete non-text, non-JSON MIME type without parameters or wildcards. Other streams stayapplication/json. SSE still usestext/event-streamwith base64 data.NoExternalWrites,NoStreamDeleteandNoInvocationDeleteremove that protocol operation (405with an exactAllowheader); leaving themfalsekeeps the operation.MaxReadersPerStreamis 1 through 16 and applies to long-poll and SSE.MaxAppendsPerSecondmust not be combined withNoExternalWrites. A limit rejection is429withRetry-After: 1; limits are per route and worker-service node, not cluster-wide.- A stream input cannot be bound from the path, query or a header.
Serving the Agent’s Own Files
Mount.ExposeFiles serves files from the agent’s filesystem under its mount, without calling a method:
var Agent = golem.DefineAgent[ID](golem.Spec{
Name: "MediaAgent",
HTTP: &golem.Mount{
Path: "/media/{name}",
ExposeFiles: []golem.FileMapping{
{Route: "/latest", Path: "/latest.txt"}, // one file
{Route: "/files/*", Path: "/public/$1"}, // a directory tree
},
},
})- The agent must be durable and not a phantom, and every
IDfield must be a scalar captured exactly once byMount.Path(no{*rest}), so a request names exactly one agent. - Mappings are tried in order; repeating a route with another path gives a fallback. A subtree route ends in
/*and its path in/$1. - Only
GETandHEADare served, with ranges and conditional requests. There is no directory listing, implicit index file or symlink following — expose only directories meant for HTTP. - Files the constructor or a method writes (with
os.WriteFile) are served as they change. Mount.FileResponseHeadersadds response headers, in order, to every file served:[]golem.FileResponseHeader{{Name: "content-security-policy", Value: "default-src 'none'"}}. Headers the platform manages itself (content-type,cache-control,etag, the CORS headers, …) are rejected at deploy.
Key Constraints
- Every
IDfield must appear as a{var}inMount.Path(else the platform can’t identify the instance); path vars may also be literals or the trailing catch-all{*rest}. - A method with an HTTP endpoint but no agent-level
Spec.HTTPmount is a definition error — set the mount. - Serving the routes requires an HTTP API deployment (see the manifest
httpApiconfiguration); mounting alone only advertises the routes.
Related Guides
| Guide | Description |
|---|---|
golem-add-agent-go | Define the agent whose methods you’re exposing |
golem-make-http-request-go | Make an outgoing HTTP request from agent code |
golem-multi-instance-agent-go | The {var} path segments select an instance by ID |