Documentation
¶
Overview ¶
Package pkg holds the controller's backend-independent control API: the request and response shapes a caller speaks over A2A, and the single interface every backend implements. Nothing in this package names a backend — that is the point of it.
Index ¶
- Constants
- func NewA2AExecutor(provider BackendProvider, backend Backend) a2asrv.AgentExecutor
- func NewA2AHandler(executor a2asrv.AgentExecutor) http.Handler
- func NewAgentCard(config AgentCardConfig) *a2a.AgentCard
- func NewAgentCardHandler(card *a2a.AgentCard) http.Handler
- type AgentCardConfig
- type Backend
- type BackendProvider
- type ControlRequest
- type ControlResponse
- type CreateRequest
- type CreateResponse
- type ListResponse
- type Operation
- type StopRequest
- type StopResponse
- type StubWorkerBackend
- type TaskName
- type Worker
- type WorkerBackend
- type WorkerID
- type WorkerState
Constants ¶
const A2APath = "/a2a"
A2APath is the route the A2A JSON-RPC binding is served at.
const AgentCardPath = a2asrv.WellKnownAgentCardPath
AgentCardPath is the well-known path the Agent Card is served at, as the A2A spec defines it. It is the SDK's own constant, re-exported so the service's wiring reads both A2A routes from one place.
Variables ¶
This section is empty.
Functions ¶
func NewA2AExecutor ¶ added in v0.2.0
func NewA2AExecutor(provider BackendProvider, backend Backend) a2asrv.AgentExecutor
NewA2AExecutor creates the A2A executor for the control API.
provider resolves a backend name to the WorkerBackend that serves it; backend is the name the controller was configured with. Both are injected — the executor reads no global and no environment — so the same executor serves any backend, and a test drives it with a stub through the real selection wiring.
func NewA2AHandler ¶ added in v0.2.0
func NewA2AHandler(executor a2asrv.AgentExecutor) http.Handler
NewA2AHandler serves the A2A JSON-RPC binding over the given executor. The body is capped before the SDK handler sees it.
func NewAgentCard ¶ added in v0.2.0
func NewAgentCard(config AgentCardConfig) *a2a.AgentCard
NewAgentCard builds the Agent Card this service advertises. Its single supported interface is the JSON-RPC binding at config.PublicURL, which is the externally reachable address supplied as configuration — never the listen address and never a value derived from a request. The card carries no credential.
Types ¶
type AgentCardConfig ¶ added in v0.2.0
type AgentCardConfig struct {
// Name names the deployed agent in the Agent Card.
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
}
AgentCardConfig is what the A2A Agent Card advertises about this service.
type Backend ¶
type Backend string
Backend names the implementation that serves workers. Its value space belongs to the backend implementations and to the selection wiring, never to this schema: a constant naming a backend here would put that backend's identity above the seam this package exists to hold, and a second backend would have to edit the shared schema to become selectable.
const ( // BackendDocker runs each worker as a container on the local host. BackendDocker Backend = "docker" // BackendKubernetes runs each worker as a workload in the cluster. BackendKubernetes Backend = "k8s" )
The backend names. They live here and nowhere else: this file is the one place above the seam that names a backend, so a backend becomes selectable by editing this selection wiring alone. The shared schema (types.go) names none of them, which is what keeps a backend's identity from becoming part of the control API.
type BackendProvider ¶ added in v0.2.0
type BackendProvider interface {
// Get returns the backend serving the named backend, or an error when the name is
// not one the controller knows or its implementation is not wired.
Get(ctx context.Context, backend Backend) (WorkerBackend, error)
}
BackendProvider resolves a backend name to the WorkerBackend that serves it.
func NewBackendProvider ¶ added in v0.2.0
func NewBackendProvider(docker WorkerBackend, kubernetes WorkerBackend) BackendProvider
NewBackendProvider creates the backend selection wiring.
docker and kubernetes are the implementations of the two backends the controller supports, in the order their names are declared above. Either may be nil while its implementation is not yet wired; Get then refuses that backend rather than handing back a nil seam that would fail on first use.
type ControlRequest ¶
type ControlRequest struct {
Operation Operation `json:"operation"`
// Create is set when Operation is OperationCreate.
Create *CreateRequest `json:"create,omitempty"`
// Stop is set when Operation is OperationStop.
Stop *StopRequest `json:"stop,omitempty"`
}
ControlRequest is one control-API call. Exactly one operation-specific field is set and it matches Operation; a request that sets none, or more than one, is refused rather than guessed at.
type ControlResponse ¶
type ControlResponse struct {
Operation Operation `json:"operation"`
Create *CreateResponse `json:"create,omitempty"`
List *ListResponse `json:"list,omitempty"`
Stop *StopResponse `json:"stop,omitempty"`
}
ControlResponse is the controller's reply to one ControlRequest. Exactly one operation-specific field is set and it matches Operation.
type CreateRequest ¶
type CreateRequest struct {
Task TaskName `json:"task"`
}
CreateRequest asks for a worker serving one task.
type CreateResponse ¶
type CreateResponse struct {
Worker Worker `json:"worker"`
// Token is the worker's own bearer credential. It must never be logged, echoed into a
// transcript, or written to a file.
Token string `json:"token"`
}
CreateResponse is the reply to a create. It is the only place a worker's token is ever returned: the controller keeps no copy it depends on, so a caller that loses this reply stops the worker and creates another rather than asking the controller to repeat it.
type ListResponse ¶
type ListResponse struct {
Workers []Worker `json:"workers"`
}
ListResponse is the reply to a list.
type Operation ¶
type Operation string
Operation names one control-API operation. It travels as a plain string so a caller written against this schema needs no shared Go type to address the controller.
const ( // OperationCreate asks the controller for a worker serving one task. OperationCreate Operation = "create" // OperationList asks the controller for every worker it owns. OperationList Operation = "list" // OperationStop asks the controller to stop and remove one worker. OperationStop Operation = "stop" )
type StopRequest ¶
type StopRequest struct {
WorkerID WorkerID `json:"worker_id"`
}
StopRequest names the worker to stop and remove.
type StopResponse ¶
type StopResponse struct {
Worker Worker `json:"worker"`
}
StopResponse is the reply to a stop. It carries the stopped worker so a caller can confirm which one the controller acted on.
type StubWorkerBackend ¶ added in v0.2.0
type StubWorkerBackend struct {
// contains filtered or unexported fields
}
StubWorkerBackend is a throwaway in-memory WorkerBackend. It exists for two reasons.
It makes the executor testable with no container, no cluster and no credential: a test drives the real executor and the real selection wiring against it. And it is the second implementation of WorkerBackend, which is what proves the seam — the executor drives it through the same interface it drives a real backend through, without knowing which one it holds.
It is absent from the selection wiring in backend-provider.go: no Backend value resolves to it. It is therefore reachable only by tests and by explicit construction, and can never serve a production request.
func NewStubWorkerBackend ¶ added in v0.2.0
func NewStubWorkerBackend() *StubWorkerBackend
NewStubWorkerBackend creates an empty in-memory backend.
func (*StubWorkerBackend) Create ¶ added in v0.2.0
func (s *StubWorkerBackend) Create( ctx context.Context, req CreateRequest, ) (*CreateResponse, error)
Create records a worker for the requested task and returns it with a token derived from its id. A task that already has a worker returns the existing one, matching the contract the real backends keep, so a repeated create never starts a second worker.
func (*StubWorkerBackend) List ¶ added in v0.2.0
func (s *StubWorkerBackend) List(ctx context.Context) (*ListResponse, error)
List returns every worker the stub holds, in no particular order.
func (*StubWorkerBackend) Stop ¶ added in v0.2.0
func (s *StubWorkerBackend) Stop(ctx context.Context, req StopRequest) (*StopResponse, error)
Stop removes the named worker and returns it, reporting it as stopped. A worker the stub does not hold is refused rather than silently ignored.
type TaskName ¶
type TaskName string
TaskName is the vault task a worker serves. It names the work, not the worker — a task has at most one worker, and a second create for a task that already has one returns the existing worker rather than starting another.
type Worker ¶
type Worker struct {
WorkerID WorkerID `json:"worker_id"`
Task TaskName `json:"task"`
A2AURL string `json:"a2a_url"`
Backend Backend `json:"backend"`
State WorkerState `json:"state"`
}
Worker is one worker as the control API reports it. It deliberately carries no token: a worker's token is returned once by create and never again, so no listing, no log and no restart can leak one.
type WorkerBackend ¶ added in v0.2.0
type WorkerBackend interface {
// Create starts one worker serving the requested task and returns it together with
// its own bearer token. A task that already has a worker returns the existing one
// rather than starting a second.
Create(ctx context.Context, req CreateRequest) (*CreateResponse, error)
// List returns every worker the backend owns. It is rebuilt from the backend's own
// resource state on every call, never from controller memory, so a controller
// restart loses nothing.
List(ctx context.Context) (*ListResponse, error)
// Stop stops and removes the named worker and returns it.
Stop(ctx context.Context, req StopRequest) (*StopResponse, error)
}
WorkerBackend is the single seam every backend implements. It is expressible with the control-API schema alone: it takes the schema's request shapes and returns its response shapes, so nothing above the seam names a backend and a new backend needs no change to the schema, to the executor or to the HTTP surface.
type WorkerID ¶
type WorkerID string
WorkerID identifies one worker the controller created. It is the value a caller passes back to stop a worker and the value list reports, so it stays stable across a controller restart: it is read back from the worker's own labels rather than minted per request and held in memory.
type WorkerState ¶
type WorkerState string
WorkerState is what list reports about a worker's lifecycle.
const ( // WorkerStateRunning means the worker exists and answers on its own A2A URL. WorkerStateRunning WorkerState = "running" // WorkerStateStopped means the worker no longer exists. It appears only in the reply to // the stop that removed it; list never returns it. WorkerStateStopped WorkerState = "stopped" // WorkerStateUnknown means the worker's backing resource exists but the controller // cannot classify it, so a caller must not read it as running. WorkerStateUnknown WorkerState = "unknown" )