Skip to main content
The AG-UI handler exposes registered agents through HTTP and server-sent events. The embedded chat client uses the same endpoints; you can also bring your own AG-UI client.

Mount the embedded UI

With agent constructed, import github.com/hastekit/agent-sdk-go/pkg/agui/web:
The UI is served at /, with a basic fallback at /basic.html. API routes are mounted under /api/agui. Static assets are embedded in the binary, so applications importing the package do not need to build the frontend themselves. For endpoints without the UI, use agui.NewHandler(registry). The route table below is relative to that handler; prepend /api/agui when using web.Handler.

Resolve a namespace

All namespace-scoped operations use one namespace resolved from the request. Configure agui.WithNamespaceResolver on the protocol or embedded handler. The resolver should read identity already established by your authentication layer; it must be safe for concurrent requests. A nil resolver, or a blank result, selects default. A resolver error returns HTTP 403. There is no WithNamespace option and no additional cross-namespace authorization callback. A request cannot opt into another namespace by choosing a thread or stream ID. The embedded UI needs no namespace selector and can use default. For a tenant-aware deployment, resolve the tenant server-side from the authenticated request. Namespace partitioning does not itself authenticate users or restrict which registered agent names they may use.

Start and stop

A minimal AG-UI run body is:
runId is optional. The handler returns X-Agui-Run-Id, X-Agui-Thread-Id, and X-Stream-Id when starting a stream. For cross-origin clients, expose the headers through your CORS policy if JavaScript needs to read them. With SDK history enabled, post the new trailing turn. WithFullHistory() is for a client that is the source of truth for history; using it with persisted history duplicates prior turns. To stop, POST {"threadId":"thread-42"} to the stop route. An optional streamId must match that thread in the resolved namespace. HTTP 202 means the stop was recorded, not that every external side effect has been reversed. A new message posted to a thread already running is queued and returns HTTP 204; keep reading its existing stream.

Reconnect without duplicating events

Record the SSE id of the last successfully processed event. Rejoin the thread’s stream with Last-Event-ID: <id>, or the lastEventId query parameter. The server resumes strictly after that event, then follows live events. Without a cursor, the endpoint replays the retained run from its opening event, including a completed run if retained. This is useful after a full reload. It is not an unlimited archive of the whole conversation; use the history endpoint for prior turns. Invalid cursors, conflicting header/query values, and cursors for another stream return HTTP 400. A cursor whose required replay is no longer available returns HTTP 410. Reload persisted history and reconnect without the expired cursor. The embedded client handles reconnect cursors and history loading. Shared replay and stop behavior across replicas requires a shared broker. A memory broker only knows events in its process. See durable execution.

Paginate history

Request ?limit=50; the accepted range is 1–200 complete turns, with 50 as the default. Responses contain threadId, messages, run, nextCursor, and hasMore. Send cursor=<nextCursor> to fetch older turns. Messages inside each page are chronological; prepend older pages to the UI. Treat the cursor as opaque and keep it scoped to the original namespace/thread. The run field describes the latest pending interruption or background work even when fetching an older page. Thread listing requires history.ThreadLister; unsupported adapters return HTTP 501. Implement history.TranscriptPageReader for efficient database pagination.

Deploy an SSE endpoint

Disable proxy buffering for event streams and configure idle timeouts to allow keepalives. agui.WithKeepalive defaults to 15 seconds. Do not apply a short blanket HTTP write timeout that cuts off valid long runs. Use the same persistence and broker infrastructure on replicas serving the same conversations. Add attachment storage to enable uploads and previews. Traces are exported through OpenTelemetry; an embedded trace browser is not currently provided.

Catalogs and management

GET /agents/{agent}/skills returns the agent’s current catalog. agui.WithSkillStore enables the user library at /skills; agui.WithMCPStore enables configuration and OAuth routes at /mcp. User skills and MCP servers are enabled unless listed in forwardedProps.skills.disable or forwardedProps.mcp.disable; developer-owned globals stay enabled. The handler also accepts frontend tool definitions in the run’s tools array. Matching tool messages continue a waiting run or resume a timed-out client tool. See frontend tools and React integration.

Internal threads and background work

Filter thread lists with group_id; the default group is default. Hidden threads are omitted unless include_hidden=true. Sub-agent threads normally belong to the parent’s thread-ID group, so include that group when enumerating children. You can still address a known child thread directly under its registered agent. History responses include context when context usage is available. run.backgroundTasks identifies pending jobs and, for async child agents, agentName, agentThreadId, and their outstanding interrupts. Resume entries for those interruptions must target the child agent and child thread. The background runner keeps the parent task pending until the child completes. For persisted messages, metadata.createdAt supplies the bundle’s timestamp (or null for old history), and metadata.senderId supplies its sender when known. Preserve this metadata in custom clients. Enable scheduled work with agui.WithRoutines(service, scheduler); see Routines for lifecycle and management.