Help us shape the future of Go! Take the 2026 Go Developer Survey

server

package
v1.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 31, 2026 License: MIT Imports: 28 Imported by: 0

Documentation

Overview

Package server serves an agent.Runner over HTTP: a REST-style session API that streams run events, adapters for the Vercel AI SDK UI message stream and AG-UI, and the server side of the A2A protocol.

Endpoints

POST   /sessions                      create an empty session → 201 {"id"}
GET    /sessions                      list sessions → {"sessions": [...]}
GET    /sessions/{id}                 messages and the resumable run, if any
DELETE /sessions/{id}                 end the session (Runner.EndSession) → 204
                                      ?reason= for SessionEnd hooks, ?purge=true also deletes it
POST   /sessions/{id}/runs            run a message (RunRequest); the session is created if needed
POST   /sessions/{id}/resume          resume the interrupted or failed run (ResumeRequest)
POST   /sessions/{id}/discard         discard the interrupted or failed run → 204
POST   /sessions/{id}/clear           Runner.Clear → 201 {"id"} of the new session
POST   /sessions/{id}/fork            Runner.Fork → 201 {"id"} of the new session
GET    /sessions/{id}/checkpoints     Runner.Checkpoints → {"checkpoints": [...]} (CheckpointInfo)
POST   /sessions/{id}/rewind          Runner.Rewind (RewindRequest) → RewindResponse
POST   /chat                          Vercel AI SDK useChat endpoint
GET    /chat/{id}/stream              useChat stream resumption; always 204
POST   /agui                          AG-UI agent endpoint (RunAgentInput)
GET    /.well-known/agent-card.json   A2A agent card
POST   /a2a                           A2A JSON-RPC endpoint

Mount the handler under a prefix with http.StripPrefix. Request bodies are JSON (Content-Type: application/json) of at most WithMaxBodyBytes.

The run and resume endpoints answer according to the Accept header:

  • text/event-stream, or no Accept header: Server-Sent Events in the protocol of WithProtocol or the protocol query parameter (agentx, vercel-ui or ag-ui).
  • application/x-ndjson or application/jsonl: one event per line, as WriteJSONL writes them.
  • application/json: the final loop.Result once the run ends, with status 200 whatever the run's status.

Errors

Errors are JSON bodies {"error": {"code": ..., "message": ...}}:

400 bad_request             invalid body, session ID or protocol
401 unauthorized            WithAuth rejected the request
403 forbidden               WithSessionAccess rejected the session
404 not_found               the session does not exist
409 session_busy            the session has a request in progress on this server
409 run_interrupted         the session waits for approvals; the body's "pending" lists them
409 nothing_to_resume       resume or discard of a session without such a run
409 conflict                session.ErrConflict: another writer saved the session
409 cannot_resume           the run's checkpoint is missing; discard it
409 cannot_rewind           agent.ErrCannotRewind: no file history, or the message is gone
413 payload_too_large       the body exceeds the limit
415 unsupported_media_type  the body is not JSON, or a file's media type is not supported
500 internal                anything else

Hooks choose another status by returning an *Error. An error after a stream has started is sent in the stream: as an "error" event with an error record {"type":"error","code":...,"message":...} in the agentx protocol and JSON Lines, an "error" chunk in the UI message stream and RUN_ERROR in AG-UI. Its code is one of the above, or run_failed for the error of a failed run.

Wire format

Events are loop.Event values encoded as JSON with snake_case names:

{"type":"message","run_id":"run_…","session_id":"s1","agent":"chat","turn":1,
 "message":{"role":"assistant","parts":[{"type":"text","text":"Hi"}]},
 "usage":{"input_tokens":10,"output_tokens":5}}

Fields that are empty are omitted. "type" is one of the runtime EventType values; the other fields are those of loop.Event, whose JSON tags, with those of the model and runtime types they hold, define the format. The golden files in testdata show every event type. Over the wire the server leaves out Result.Messages, the whole conversation (GET /sessions/{id} returns it), and the run state of interrupts, which holds the system prompt; Result.new_messages holds the run's messages and Result.Err is the string "error". The arguments of a tool call in a delta event are a fragment of JSON text, so they are sent as a JSON string; concatenate the fragments of a part to get the arguments. Media parts, in messages and in tool results, hold their data as base64 in "data"; tool results also carry the tool's "metadata". A handoff event holds "handoff" ({"from", "to", "tool", "call_id", "input"}), and a result names the agent that ran last in "agent" and the guardrail that blocked the run in "guardrail".

Files and media

User messages of every protocol may carry files: images, audio, video, text, PDF and Office documents (image/*, audio/*, video/*, text/*, application/pdf, application/msword, application/vnd.ms-excel and application/vnd.openxmlformats-officedocument.*), inline as data or a data: URL or by URL. They become image, audio, video or document parts by media type; whether the model takes them depends on its provider. Other media types are 415 unsupported_media_type.

Guardrails

An answer an output guardrail blocks is not kept: the session holds "[response withheld by guardrail <name>]" in its place. Guardrails check an answer once the model has written it, so by default its text has already streamed; with guard.HoldOutput the runtime holds each response's events until it calls tools or the guardrails pass, and no protocol ever sends a blocked answer.

Rewind

GET /sessions/{id}/checkpoints lists the session's checkpoints and POST /sessions/{id}/rewind {"checkpoint_id": "run_…", "mode": "both"} returns the session to one, as agent.Runner.Rewind does: mode "code" restores the files of the agent's file history, "conversation" removes the checkpoint's message and what followed and returns the message, "both" does both. A session waiting for approvals cannot be rewound.

In the agentx protocol each event is a Server-Sent Event named after its type, with the event's sequence number in the stream as its ID:

id: 3
event: delta
data: {"type":"delta","run_id":"run_…",…}

Streams send a comment line every WithHeartbeat while idle. They end after the run_end event, or the error that follows it when the run failed. Last-Event-ID is not supported: streams are not buffered.

Runs and sessions

A session runs one request at a time on a server; another request for a busy session gets 409 session_busy instead of waiting. A new session is stored when its first run ends. If the client of a stream goes away, the run is cancelled as if its consumer stopped iterating, and the session is saved. Given a checkpointer (agent.WithCheckpointer) the run then ends as a failed run that POST /sessions/{id}/resume continues; the next run gives it up.

A run that needs approvals ends with status interrupted. The approvals it waits for are in its run_end event and in GET /sessions/{id}; resume it with {"approvals": {"<call_id>": {"approved": true}}}. The server remembers interrupts in memory; give it the checkpointer with WithCheckpointer so it can report them after a restart.

Run requests may set max_turns and limits only with AllowRunOptions; WithRunOptions sets run options on the server side.

Vercel AI SDK

POST /chat implements the UI message stream protocol v1 (https://ai-sdk.dev/docs/ai-sdk-ui/stream-protocol) for useChat with the default transport (api: "/chat"). The chat ID is the session ID and only the last message is read, since the session holds the conversation. Each run is one assistant message: start, start-step and finish-step per turn, text-*, reasoning-* and tool-input-* chunks as the model streams (or whole when it does not), tool-output-available, tool-output-error and tool-output-denied for results, error, and finish with the run's status, ID and usage in messageMetadata.agentx, then [DONE]. A guardrail block finishes with "content-filter" and the guardrail in messageMetadata.agentx.guardrail, which also names the agent that ran last. Media in assistant messages and tool results are file parts (data: URLs for inline data); handoffs and answers sent back for not matching the output schema are data-handoff and data-output-retry parts. User messages take file parts. Approvals use the AI SDK's tool approval flow: a tool-approval-request whose approvalId is the tool call ID ends the stream with finishReason "tool-calls", and the next request, whose last message is the assistant message with approval-responded tool parts (for example with sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithApprovalResponses), resumes the run. Tool calls of sub-agents that need approval are sent as dynamic tools. Not supported: the regenerate-message trigger, stream resumption, and events of sub-agents, which are left out.

AG-UI

POST /agui implements AG-UI 1.0 events over Server-Sent Events (https://docs.ag-ui.com/concepts/events): RUN_STARTED, RUN_FINISHED with a success or interrupt outcome and usage, RUN_ERROR, STEP_STARTED and STEP_FINISHED per turn, TEXT_MESSAGE_*, REASONING_*, TOOL_CALL_START, TOOL_CALL_ARGS, TOOL_CALL_END and TOOL_CALL_RESULT, and SUBAGENT_STARTED, SUBAGENT_FINISHED and SUBAGENT_ERROR for sub-agents whose events are forwarded, and CUSTOM events named "handoff" and "output_retry". A run a guardrail blocked ends with RUN_ERROR code "blocked". The thread ID is the session ID; the last message, a user message, is the input: text, and image, audio, video and document parts whose source is data (base64), a URL or a provider file ID; a "filename" in a part's metadata names its file. Media in assistant messages and tool results are not sent, as AG-UI has no events for them. Interrupts (https://docs.ag-ui.com/concepts/interrupts) have reason "tool_call", the tool call ID as their ID and a response schema {"approved": boolean, "reason": string, "editedArgs": object}; answer them with resume entries on the same thread. Not supported: frontend tools, state and messages snapshots, and the binary encoding.

A2A

POST /a2a implements the JSON-RPC binding of A2A 1.0 (https://a2a-protocol.org/latest/specification/). Requests must send A2A-Version: 1.0; others get VersionNotSupportedError. Methods: SendMessage (blocking, or with returnImmediately), SendStreamingMessage, GetTask, ListTasks, CancelTask and SubscribeToTask. Push notification methods return PushNotificationNotSupportedError and GetExtendedAgentCard returns ExtendedAgentCardNotConfiguredError. The gRPC and HTTP+JSON bindings are not implemented.

A message's contextId is the session ID; one is generated if it is missing. A message without taskId starts a task, which runs in the background until it completes, fails, is canceled or requires input, whether or not the client keeps listening. Text and data parts and files (raw or by URL) are accepted. Interim assistant messages with tool calls and handoffs are reported as working status updates; the final answer is the text artifact "output". A run a guardrail blocked puts the task in TASK_STATE_REJECTED; the task metadata's "agentx" object names the guardrail and the agent that ran last. A run that needs approvals puts the task in TASK_STATE_INPUT_REQUIRED with the requests in a data part; reply with the taskId and a data part {"approved": true} or {"approvals": {"<call_id>": {"approved": false, "reason": "..."}}}. Tasks are kept in memory, at most A2AConfig.MaxTasks of them. Access to a new context that WithSessionAccess denies is an HTTP error; tasks of such contexts are reported as not found.

Index

Constants

View Source
const (
	CodeBadRequest       = "bad_request"
	CodeUnauthorized     = "unauthorized"
	CodeForbidden        = "forbidden"
	CodeNotFound         = "not_found"
	CodeSessionBusy      = "session_busy"
	CodeRunInterrupted   = "run_interrupted"
	CodeNothingToResume  = "nothing_to_resume"
	CodeConflict         = "conflict"
	CodeCannotResume     = "cannot_resume"
	CodeCannotRewind     = "cannot_rewind"
	CodePayloadTooLarge  = "payload_too_large"
	CodeUnsupportedMedia = "unsupported_media_type"
	CodeRunFailed        = "run_failed"
	CodeInternal         = "internal"
)

Error codes of the JSON error bodies.

View Source
const A2AVersion = "1.0"

A2AVersion is the A2A protocol version the server implements.

View Source
const DefaultA2AMaxTasks = 1000

DefaultA2AMaxTasks bounds the A2A tasks the server remembers unless A2AConfig.MaxTasks changes it.

View Source
const DefaultHeartbeat = 15 * time.Second

DefaultHeartbeat is the interval of the comment lines that keep idle event streams open.

View Source
const DefaultMaxBodyBytes = 1 << 20

DefaultMaxBodyBytes bounds request bodies unless WithMaxBodyBytes changes it.

Variables

This section is empty.

Functions

func WriteJSONL

func WriteJSONL(w io.Writer, seq iter.Seq2[*loop.Event, error]) (*loop.Result, error)

WriteJSONL writes the events of a run to w as JSON Lines, one event per line in the wire format of the package documentation, for example for a command's machine-readable output. An error of the run is written as a line {"type":"error","code":...,"message":...} and returned with the run's result, like loop.Collect.

Types

type A2AConfig

type A2AConfig struct {
	// Name, Description and Version identify the agent. They default to
	// "agentx", "An agentx agent." and the framework version.
	Name        string
	Description string
	Version     string
	// URL is the URL of the JSON-RPC endpoint, POST /a2a. By default it is
	// derived from the request for the card, which is wrong when the
	// handler is mounted under a prefix.
	URL              string
	Provider         *A2AProvider
	DocumentationURL string
	IconURL          string
	// Skills default to one "chat" skill with the agent's description.
	Skills []A2ASkill
	// SecuritySchemes and SecurityRequirements are copied into the card
	// as they are, to describe the authentication WithAuth checks.
	SecuritySchemes      map[string]any
	SecurityRequirements []map[string]any
	// MaxTasks bounds the tasks the server remembers; the oldest finished
	// tasks are forgotten first. It defaults to DefaultA2AMaxTasks.
	MaxTasks int
}

A2AConfig describes the agent in its A2A agent card.

type A2AProvider

type A2AProvider struct {
	Organization string `json:"organization"`
	URL          string `json:"url"`
}

A2AProvider is the provider of an agent in its card.

type A2ASkill

type A2ASkill struct {
	ID          string   `json:"id"`
	Name        string   `json:"name"`
	Description string   `json:"description"`
	Tags        []string `json:"tags"`
	Examples    []string `json:"examples,omitempty"`
	InputModes  []string `json:"inputModes,omitempty"`
	OutputModes []string `json:"outputModes,omitempty"`
}

A2ASkill is a skill of an agent in its card.

type CheckpointInfo

type CheckpointInfo struct {
	// ID is the ID of the run the checkpoint precedes.
	ID string `json:"id"`
	// Index is the position of the run's user message in the session's
	// messages, or -1 when only the code can be rewound.
	Index   int            `json:"index"`
	Message *model.Message `json:"message,omitempty"`
	Time    time.Time      `json:"time"`
	// Agent is the agent that was active at the checkpoint, if not the
	// Runner's own.
	Agent string `json:"agent,omitempty"`
	// Changes are the file changes of the run; Code reports whether
	// rewinding the code would restore files.
	Changes []builtin.FileChange `json:"changes,omitempty"`
	Code    bool                 `json:"code"`
}

CheckpointInfo is an item of GET /sessions/{id}/checkpoints; see agent.Checkpoint.

type Error

type Error struct {
	Status  int    `json:"-"`
	Code    string `json:"code"`
	Message string `json:"message"`
}

Error is an error with an HTTP status and a machine-readable code. Hooks such as WithAuth return one to choose the response, for example a 403.

func (*Error) Error

func (e *Error) Error() string

type Option

type Option func(*config)

Option configures a Server.

func AllowRunOptions

func AllowRunOptions() Option

AllowRunOptions lets clients set max_turns and limits of a run in the request body. Without it such requests are rejected, because they would let clients replace the agent's limits.

func WithA2A

func WithA2A(cfg A2AConfig) Option

WithA2A configures the A2A agent card and task retention.

func WithAuth

func WithAuth(fn func(r *http.Request) error) Option

WithAuth authenticates every request except CORS preflights. A non-nil error rejects the request with 401, or with the status of an *Error.

func WithCORS

func WithCORS(origins ...string) Option

WithCORS allows cross-origin requests from origins; "*" allows any origin. Preflight requests are answered without authentication.

func WithCheckpointer

func WithCheckpointer(cp loop.Checkpointer) Option

WithCheckpointer gives the server the agent's checkpointer, so it can report the approvals an interrupted run waits for after a restart. The server otherwise remembers them only for runs it saw interrupted.

func WithHeartbeat

func WithHeartbeat(d time.Duration) Option

WithHeartbeat sets the interval of the comment lines sent on idle event streams. It defaults to DefaultHeartbeat; zero or less disables them.

func WithLogger

func WithLogger(l *slog.Logger) Option

WithLogger logs server errors.

func WithMaxBodyBytes

func WithMaxBodyBytes(n int64) Option

WithMaxBodyBytes bounds request bodies; larger ones are rejected with 413. It defaults to DefaultMaxBodyBytes.

func WithProtocol

func WithProtocol(p Protocol) Option

WithProtocol sets the encoding of event streams of the session endpoints. It defaults to ProtocolAgentX; the protocol query parameter overrides it per request.

func WithRunOptions

func WithRunOptions(fn func(r *http.Request) []agent.RunOption) Option

WithRunOptions adds options to every run and resumed run started by a request, for example limits chosen by the caller's plan. They apply after options from the request body.

func WithSessionAccess

func WithSessionAccess(fn func(r *http.Request, sessionID string) error) Option

WithSessionAccess authorizes access to a session, whether its ID comes from the path, a chat or thread ID, or an A2A context ID. A non-nil error rejects the request with 403, or with the status of an *Error. GET /sessions lists only the sessions it allows.

type Pending

type Pending struct {
	RunID string `json:"run_id"`
	// Kind is "interrupted" or "failed".
	Kind string `json:"kind"`
	// Interrupt lists the approvals an interrupted run waits for, when the
	// server knows them.
	Interrupt *loop.Interrupt `json:"interrupt,omitempty"`
}

Pending describes a session's run that can be resumed.

type Protocol

type Protocol string

Protocol selects the encoding of streamed run events.

const (
	// ProtocolAgentX streams runtime events as Server-Sent Events named
	// after the event type; see the package documentation.
	ProtocolAgentX Protocol = "agentx"
	// ProtocolVercel streams the Vercel AI SDK UI message stream, which the
	// AI SDK's useChat reads.
	ProtocolVercel Protocol = "vercel-ui"
	// ProtocolAGUI streams AG-UI events.
	ProtocolAGUI Protocol = "ag-ui"
)

type ResumeRequest

type ResumeRequest struct {
	// Approvals answers the approval requests of an interrupted run, keyed
	// by tool call ID. A failed run is resumed without approvals.
	Approvals map[string]loop.Approval `json:"approvals,omitempty"`
}

ResumeRequest is the body of POST /sessions/{id}/resume.

type RewindRequest

type RewindRequest struct {
	// CheckpointID is the ID of a checkpoint listed by
	// GET /sessions/{id}/checkpoints.
	CheckpointID string `json:"checkpoint_id"`
	// Mode is "code", "conversation" or "both"; see agent.RewindMode.
	Mode string `json:"mode"`
}

RewindRequest is the body of POST /sessions/{id}/rewind.

type RewindResponse

type RewindResponse struct {
	// Message is the user message removed from the conversation.
	Message  *model.Message       `json:"message,omitempty"`
	Restored []builtin.FileChange `json:"restored,omitempty"`
	Skipped  []builtin.FileChange `json:"skipped,omitempty"`
	Undo     string               `json:"undo,omitempty"`
}

RewindResponse is the body of the response to POST /sessions/{id}/rewind; see agent.RewindResult.

type RunRequest

type RunRequest struct {
	// Input is the text of the user message.
	Input string `json:"input,omitempty"`
	// Message is the user message; its role is set to user.
	Message *model.Message `json:"message,omitempty"`
	// MaxTurns and Limits override the agent's limits for this run; see
	// AllowRunOptions.
	MaxTurns int          `json:"max_turns,omitempty"`
	Limits   *loop.Limits `json:"limits,omitempty"`
}

RunRequest is the body of POST /sessions/{id}/runs. Exactly one of Input and Message is set.

type Server

type Server struct {
	// contains filtered or unexported fields
}

Server serves an agent Runner over HTTP. See the package documentation for its endpoints and wire formats.

func New

func New(runner *agent.Runner, opts ...Option) *Server

New returns a server for runner.

func (*Server) Close

func (s *Server) Close() error

Close is Shutdown without a deadline.

func (*Server) ServeHTTP

func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP implements http.Handler.

func (*Server) Shutdown added in v1.1.0

func (s *Server) Shutdown(ctx context.Context) error

Shutdown cancels the A2A tasks running in the background and waits for them to save their sessions until ctx ends, when it returns the cause of ctx. Tasks requested after Shutdown started see a canceled context and run in their request. Shutdown can be called again, for example with a longer deadline. It does not stop the HTTP server serving s; shut that down first.

type SessionInfo

type SessionInfo struct {
	SessionSummary
	// Messages is the conversation.
	Messages []*model.Message `json:"messages"`
	// Pending is the run Resume continues, if any.
	Pending *Pending `json:"pending,omitempty"`
}

SessionInfo is the body of GET /sessions/{id}.

type SessionSummary

type SessionSummary struct {
	ID           string    `json:"id"`
	MessageCount int       `json:"message_count"`
	CreatedAt    time.Time `json:"created_at"`
	UpdatedAt    time.Time `json:"updated_at"`
	// Busy reports a request in progress on this server.
	Busy bool `json:"busy,omitempty"`
}

SessionSummary is an item of GET /sessions.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL