Skip to Content
How-To GuidesScalaAdding HTTP Endpoints to a Scala Golem Agent

Adding HTTP Endpoints to a Scala Golem Agent

Overview

Golem agents can be exposed over HTTP using code-first route definitions. This involves:

  1. Adding a mount parameter to @agentDefinition
  2. Annotating methods with @endpoint
  3. Adding an httpApi deployment section to golem.yaml (See the golem-configure-api-domain guide)
GuideDescription
golem-http-params-scalaPath/query/header variable mapping, body mapping, supported types, response mapping
golem-make-http-request-scalaMaking outgoing HTTP requests from agent code, especially when calling other Golem agent endpoints (required for correct JSON body formatting)
golem-add-http-auth-scalaEnabling authentication
golem-add-cors-scalaConfiguring CORS allowed origins
golem-configure-api-domainSetting up httpApi in golem.yaml, security schemes, domain deployments

Steps

  1. Add mount = "/path/{param}" to @agentDefinition(...)
  2. Add @endpoint(method = "GET", path = "/...") to trait methods
  3. Add httpApi deployment to golem.yaml (see golem-configure-api-domain guide)
  4. Build and deploy

Mount Path

The mount parameter on @agentDefinition defines the base HTTP path. Path variables in {braces} map to constructor parameters defined in the class Id(...):

import golem.runtime.annotations.{agentDefinition, endpoint} import golem.BaseAgent import scala.concurrent.Future @agentDefinition(mount = "/api/tasks/{name}") trait TaskAgent extends BaseAgent { class Id(val name: String) // methods... }

Constructor Parameter Naming in Scala

The path variable names must exactly match the class Id parameter names — do not use kebab-case in path variables. Use the same camelCase (or single-word) identifier that appears in the class Id:

  • Single parameter: class Id(val name: String) → use {name} in path
  • Multiple parameters: class Id(val arg0: String, val arg1: Int) → use {arg0}, {arg1} in path
  • Custom Id class with @id: use the parameter names from the annotated class
// ✅ Correct — path variable matches parameter name exactly @agentDefinition(mount = "/api/tasks/{taskName}") trait TaskAgent extends BaseAgent { class Id(val taskName: String) } // ❌ Wrong — {task-name} does not match parameter name taskName @agentDefinition(mount = "/api/tasks/{task-name}") trait TaskAgent extends BaseAgent { class Id(val taskName: String) }
// Named parameters with @id annotation import golem.runtime.annotations.id @agentDefinition(mount = "/api/catalog/{region}/{catalog}") trait CatalogAgent extends BaseAgent { @id class CatalogParams(val region: String, val catalog: String) }

Rules:

  • Path must start with /
  • Every constructor parameter must appear as a {variable} in the mount path
  • Every {variable} must exactly match a constructor parameter name (same casing)
  • Catch-all {*rest} variables are not allowed in mount paths

Endpoint Annotation

The @endpoint annotation marks a method as an HTTP endpoint. Specify the HTTP method and path:

@endpoint(method = "GET", path = "/items") def listItems(): Future[List[Item]] @endpoint(method = "POST", path = "/items") def createItem(name: String, count: Int): Future[Item] @endpoint(method = "PUT", path = "/items/{id}") def updateItem(id: String, name: String): Future[Item] @endpoint(method = "DELETE", path = "/items/{id}") def deleteItem(id: String): Future[Unit]

Endpoint paths are relative to the mount path. Supported HTTP methods: GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS, CONNECT, TRACE, or any custom string.

For details on how path variables, query parameters, headers, and request bodies map to method parameters, See the golem-http-params-scala guide.

Phantom Agents

Set phantomAgent = true to create a new agent instance for each HTTP request, enabling fully parallel processing:

@agentDefinition( mount = "/webhook/{agent-type}/{value}", phantomAgent = true ) trait WebhookHandler extends BaseAgent { class Id(val value: String) // Each HTTP request gets its own agent instance }

Custom Types

All types used in endpoint parameters and return values must have a zio.blocks.schema.Schema instance:

import zio.blocks.schema.Schema final case class Task(id: String, title: String, done: Boolean) derives Schema

For collections, use List[T] instead of Array[T]. Array does not have automatic Schema derivation support.

Return Type to HTTP Response Mapping

Golem maps method return types to HTTP status codes and response bodies according to the table below. This mapping is currently not configurable.

Scala Either[E, T] is mapped to the WIT result<T, E> type, with Right treated as Ok and Left as Err.

Return TypeHTTP StatusResponse Body
Future[Unit]204 No Contentempty
Future[T]200 OKJSON-serialized T
Future[Option[T]]200 OK if Some, 404 Not Found if NoneJSON T or empty
Future[Either[E, T]]200 OK if Right, 500 Internal Server Error if LeftJSON T or JSON E
Future[Either[E, Unit]]204 No Content if Right, 500 if Leftempty or JSON E
Future[Either[Unit, T]]200 OK if Right, 500 if LeftJSON T or empty
Future[UnstructuredBinary]200 OKRaw binary with Content-Type

Complete Example

import golem.runtime.annotations.{agentDefinition, agentImplementation, endpoint, header} import golem.BaseAgent import zio.blocks.schema.Schema import scala.concurrent.Future final case class Task(id: String, title: String, done: Boolean) derives Schema @agentDefinition(mount = "/task-agents/{name}") trait TaskAgent extends BaseAgent { class Id(val name: String) @endpoint(method = "GET", path = "/tasks") def getTasks(): Future[List[Task]] @endpoint(method = "POST", path = "/tasks") def createTask(title: String): Future[Task] @endpoint(method = "GET", path = "/tasks/{id}") def getTask(id: String): Future[Option[Task]] @endpoint(method = "POST", path = "/report") def submitReport(@header("X-Tenant") tenantId: String, data: String): Future[String] }
import golem.runtime.annotations.agentImplementation import scala.concurrent.Future @agentImplementation() final class TaskAgentImpl(private val name: String) extends TaskAgent { private var tasks: List[Task] = Nil override def getTasks(): Future[List[Task]] = Future.successful(tasks) override def createTask(title: String): Future[Task] = Future.successful { val task = Task(id = (tasks.length + 1).toString, title = title, done = false) tasks = tasks :+ task task } override def getTask(id: String): Future[Option[Task]] = Future.successful(tasks.find(_.id == id)) override def submitReport(tenantId: String, data: String): Future[String] = Future.successful(s"Report from $tenantId: $data") }
# golem.yaml (add to existing file) httpApi: deployments: local: - domain: my-app.localhost:9006 agents: TaskAgent: {}

Key Constraints

  • A mount path is required on @agentDefinition before any @endpoint annotations can be used
  • All constructor parameters (from class Id) must be provided via mount path variables
  • Path/query variable names must exactly match method parameter names
  • Header parameters use @header("Header-Name") annotation on individual method parameters
  • Catch-all path variables {*name} can only appear as the last path segment
  • The endpoint path must start with /
  • Custom types require a zio.blocks.schema.Schema instance (use derives Schema in Scala 3)
  • The scalacOptions += "-experimental" flag is required for macro annotations
Last updated on