Documentation
¶
Overview ¶
Package interactive serves the shared interactive agent HTTP surface: readiness, metrics and prompt intake, over a per-session cache of long-lived conversations.
Index ¶
Constants ¶
const DefaultMaxSessions = 8
DefaultMaxSessions is the number of sessions the cache holds at once when the caller supplies a non-positive maximum. A session that keeps serving turns is never idle, so idle eviction alone bounds accumulation but not concurrency; this is the bound on how many conversations are held simultaneously.
The value comes from three measured inputs, not from dividing the limit by the per-session cost: one held session costs roughly 88 MiB on the deployed Claude-backed service, the container's memory limit is 1 GiB, and the limit must leave honest headroom for the Go runtime and service baseline plus the overshoot the soft limit permits while every candidate is mid-turn. Eight sessions hold about 704 MiB and leave roughly 31% of the limit free for those allowances. It is exported so a caller can name the effective bound it gets when it passes a non-positive value, and so a test can assert the normalisation rather than the argument.
const DefaultSessionIdleTimeout = 15 * time.Minute
DefaultSessionIdleTimeout is the idle period a non-positive configured value is replaced with. A session that has gone this long without serving a turn is closed and dropped from the cache. It is exported so a caller can name the effective bound it gets when it passes a non-positive value, and so a test can assert the normalisation rather than the argument.
Variables ¶
var AuthDisabled = Auth{/* contains filtered or unexported fields */}
AuthDisabled is the explicit opt-out: a service built with it serves every route without authentication.
It exists so that a consumer which is deliberately unexposed states that choice in one visible, greppable line rather than inheriting a default. Passing it on a pod that is later exposed serves that surface unauthenticated — the residual risk this design accepts — so grep for it before putting anything in front of the port.
Functions ¶
func A2APublicURLFromEnv ¶ added in v0.96.0
A2APublicURLFromEnv reads the externally reachable A2A address from the process environment.
It returns an error when the variable is unset or empty, so a service that cannot advertise a real endpoint fails to start rather than advertising the container-local listen address. The address is public by design and is not a credential.
Types ¶
type Auth ¶ added in v0.94.0
type Auth struct {
// contains filtered or unexported fields
}
Auth is a service's authentication decision: either the bearer token every gated request must carry, or an explicit opt-out.
The zero value is NOT a usable default — it refuses every gated request. A service built with the zero value authenticates nothing and serves only the exempt routes, which is the fail-closed outcome: a decision left unmade cannot silently open the surface. Build a real decision with NewAuthToken or AuthFromEnv, or state the opt-out with AuthDisabled.
func AuthFromEnv ¶ added in v0.94.0
AuthFromEnv reads the bearer token from the process environment and returns the Auth that requires it.
It returns an error when the variable is unset or empty, so a service that cannot authenticate fails to start rather than serving unauthenticated. The token value is never logged, never returned inside the error and never written anywhere.
func NewAuthToken ¶ added in v0.94.0
NewAuthToken returns the Auth that requires every gated request to present token as its bearer credential. An empty token is not a usable credential: it yields the same fail-closed value as the zero Auth.
type CardConfig ¶ added in v0.97.0
type CardConfig struct {
// Name names the deployed agent in the Agent Card. Empty means the card keeps
// the library default, "interactive".
Name string
// PublicURL is the externally reachable A2A endpoint the card advertises
// verbatim. It is never derived from the listen address or a request header.
PublicURL string
}
CardConfig is what the A2A Agent Card advertises about this service.
type PendingPermission ¶ added in v0.93.0
type PendingPermission struct {
// ID identifies the request. It is generated per request and is the only handle
// a verdict may name.
ID string `json:"id"`
// ToolName is the name of the tool the CLI wants to invoke.
ToolName string `json:"tool_name"`
// Description is the CLI's human-readable description of the invocation.
Description string `json:"description"`
// InputPreview is a preview of the tool input. It is never logged.
InputPreview string `json:"input_preview"`
}
PendingPermission is one permission request currently waiting for a verdict, as served on GET /permission.
type PermissionRegistry ¶ added in v0.93.0
type PermissionRegistry interface {
// DecidePermission registers request as pending and blocks until a verdict is
// posted for it or ctx is cancelled.
DecidePermission(
ctx context.Context,
request claude.PermissionRequest,
) (claude.PermissionDecision, error)
// List returns the requests currently pending. The result is never nil, so the
// endpoint always renders a JSON array.
List() []PendingPermission
// Resolve delivers decision to the pending request named by id and reports
// whether such a request was pending. It never blocks.
Resolve(id string, decision claude.PermissionDecision) bool
}
PermissionRegistry holds the permission requests one service is currently waiting on. It is the PermissionDecider the sessions built for that service consult, so a request raised by a turn appears on that service's endpoint and nowhere else.
func NewPermissionRegistry ¶ added in v0.93.0
func NewPermissionRegistry() PermissionRegistry
NewPermissionRegistry creates an empty registry. Construct one per service and pass the same instance to the session factory and to NewServiceWithPermissions, so a request raised by a session is served by that service's endpoint.
type Service ¶
type Service interface {
// Handler returns the HTTP handler serving GET /readiness, GET /metrics and
// POST /prompt, plus POST /a2a (the A2A JSON-RPC binding), plus GET and POST
// /permission when the service was built with a permission registry, plus the A2A
// Agent Card at the well-known path. Every route except /readiness, /metrics and
// the Agent Card requires the configured bearer token; those three are exempt
// because a kubelet probe and a Prometheus scrape cannot carry one without the
// token being written into the pod spec and the scrape configuration, and
// discovery of the card is public by design.
Handler() http.Handler
// Run serves the handler on the configured listen address until ctx is cancelled.
Run(ctx context.Context) error
}
Service serves the interactive agent HTTP surface.
func NewService ¶
func NewService( sessions agentlib.SessionFactory, listen string, providerBaseURL string, registry *prometheus.Registry, auth Auth, card CardConfig, sessionIdleTimeout time.Duration, maxSessions int, ) Service
NewService creates the interactive session service.
sessions supplies one conversation per session id; listen is the address the service binds; providerBaseURL is the endpoint the readiness probe dials (empty means the check is skipped and reported as such); registry is the Prometheus registry the metrics route gathers from — a parameter rather than a library singleton, so each binary keeps its own metrics identity; auth is the authentication every gated route requires, and the zero Auth refuses every gated request; card names the agent in the Agent Card and carries the externally reachable address the card advertises, which is never derived from listen; sessionIdleTimeout is how long a session may go without serving a turn before its conversation is closed and dropped from the cache, and a non-positive value is replaced with DefaultSessionIdleTimeout rather than disabling eviction; maxSessions is how many sessions the cache holds at once, and a non-positive value is replaced with DefaultMaxSessions rather than disabling the limit. Both bounds are parameters rather than package constants on purpose: the right value is a function of the container's memory limit, which lives in the deployment rather than in this library, so a caller with a smaller pod must be able to state a smaller limit without waiting for a library release. The permission endpoint is not served by this constructor; use NewServiceWithPermissions to serve it.
func NewServiceWithPermissions ¶ added in v0.93.0
func NewServiceWithPermissions( sessions agentlib.SessionFactory, listen string, providerBaseURL string, registry *prometheus.Registry, auth Auth, card CardConfig, permissions PermissionRegistry, sessionIdleTimeout time.Duration, maxSessions int, ) Service
NewServiceWithPermissions creates the interactive session service with the permission endpoint enabled.
permissions is the registry the endpoint serves and the decider the sessions built by the caller's factory consult; the caller constructs it first and passes the one instance to both, which is what makes the endpoint and the sessions resolve through the same registry. A nil permissions is invalid here — use NewService for that. auth is the authentication every gated route requires, and the zero Auth refuses every gated request. card names the agent in the Agent Card and carries the externally reachable address the card advertises, which is never derived from listen. sessionIdleTimeout is how long a session may go without serving a turn before its conversation is closed and dropped from the cache; a non-positive value is replaced with DefaultSessionIdleTimeout rather than disabling eviction, so the bound cannot be switched off by a misconfigured period. maxSessions is how many sessions the cache holds at once; a non-positive value is replaced with DefaultMaxSessions rather than disabling the limit, for the same reason. Like sessionIdleTimeout it is a parameter rather than a package constant because the right value depends on the container's memory limit, which lives in the deployment rather than in this library.