Documentation
¶
Overview ¶
Package gateway is the SDK for building Squadron gateways: subprocess integrations that bridge Squadron to external systems (Discord, Slack, PagerDuty, custom dashboards, …).
Squadron loads a gateway the same way it loads a plugin — declared in HCL with `gateway "name" { source = "github.com/..."; version = "..." }`, downloaded from the GitHub release on first load, and run as a managed subprocess for the lifetime of the squadron process.
The protocol is bidirectional gRPC:
- Squadron pushes events to the gateway (OnHumanInputRequested, OnHumanInputResolved, …) so the gateway can mirror Squadron state into its external system.
- The gateway pulls / mutates state by calling Squadron's API (ListHumanInputs, ResolveHumanInput, …) so user actions in the external system flow back to Squadron.
Catch-up after disconnects is the gateway's responsibility: persist a checkpoint timestamp locally, and on startup call ListHumanInputs with `since=<checkpoint>` to backfill anything missed before live pushes resume.
Index ¶
- Constants
- func HostHandshake() plugin.HandshakeConfig
- func Serve(impl Gateway)
- type FileAttachment
- type GRPCGatewayClient
- func (g *GRPCGatewayClient) Configure(ctx context.Context, settings map[string]string) error
- func (g *GRPCGatewayClient) MessageToolSpec(ctx context.Context) (MessageToolSpec, error)
- func (g *GRPCGatewayClient) OnHumanInputRequested(ctx context.Context, rec HumanInputRecord) error
- func (g *GRPCGatewayClient) OnHumanInputResolved(ctx context.Context, rec HumanInputRecord) error
- func (g *GRPCGatewayClient) OnNotification(ctx context.Context, rec NotificationRecord) error
- func (g *GRPCGatewayClient) PostMessage(ctx context.Context, req PostMessageRequest) error
- func (g *GRPCGatewayClient) Shutdown(ctx context.Context) error
- type Gateway
- type HostPlugin
- type HumanInputFilter
- type HumanInputRecord
- type HumanInputState
- type MessageToolSpec
- type NotificationRecord
- type PostMessageRequest
- type ResolveResult
- type SquadronAPI
Constants ¶
const ( HandshakeProtocolVersion uint = 1 HandshakeMagicCookieKey = "SQUADRON_GATEWAY" HandshakeMagicCookieValue = "squadron-gateway-v1" )
Handshake config — must match between squadron's loader and any gateway binary that wants squadron to consume it.
Squadron runs hashicorp/go-plugin's handshake against this; mismatched magic cookies cause the subprocess to abort cleanly with a helpful error rather than running detached.
const MaxGRPCMessageBytes = 32 << 20 // 32 MiB
MaxGRPCMessageBytes is the gRPC message-size limit for the gateway<->squadron channel. It is raised well above gRPC's 4 MB default so file attachments (shipped as raw bytes in PostMessageRequest) fit. Squadron caps individual attachments below this.
const PluginName = "gateway"
PluginName is the well-known plugin name registered with go-plugin. Both sides reference the same string when looking up the gRPC plugin.
Variables ¶
This section is empty.
Functions ¶
func HostHandshake ¶
func HostHandshake() plugin.HandshakeConfig
HostHandshake returns the handshake config the host should use when constructing a plugin.Client. Symmetric with the value the gateway uses on Serve — mismatched values prevent the connection.
func Serve ¶
func Serve(impl Gateway)
Serve is the entry point for a gateway binary. Call it from main() after constructing the gateway implementation. Serve blocks until squadron tears down the subprocess.
Gateway authors do not need to know anything about hashicorp/go-plugin or gRPC — the SDK handles handshake, broker setup, and the bidirectional connection to squadron's SquadronAPI service.
Types ¶
type FileAttachment ¶ added in v0.0.3
FileAttachment is a squadron-local file (memory/scratchpad/packet) that squadron has already read and is shipping as raw bytes for the gateway to upload to its external system.
type GRPCGatewayClient ¶
type GRPCGatewayClient struct {
// contains filtered or unexported fields
}
GRPCGatewayClient is the host-side handle on a running gateway. It is returned from plugin.NewClient(...).Client().Dispense(...) and satisfies the GatewayClient interface that squadron uses.
func (*GRPCGatewayClient) Configure ¶
Configure pushes settings to the gateway and tells it the broker stream id where SquadronService is being served. After this returns successfully the gateway has a working SquadronAPI client and is ready to receive event pushes.
func (*GRPCGatewayClient) MessageToolSpec ¶ added in v0.0.2
func (g *GRPCGatewayClient) MessageToolSpec(ctx context.Context) (MessageToolSpec, error)
MessageToolSpec fetches the post-tool spec from the gateway.
func (*GRPCGatewayClient) OnHumanInputRequested ¶
func (g *GRPCGatewayClient) OnHumanInputRequested(ctx context.Context, rec HumanInputRecord) error
OnHumanInputRequested forwards a new request event to the gateway.
func (*GRPCGatewayClient) OnHumanInputResolved ¶
func (g *GRPCGatewayClient) OnHumanInputResolved(ctx context.Context, rec HumanInputRecord) error
OnHumanInputResolved forwards a resolution event to the gateway.
func (*GRPCGatewayClient) OnNotification ¶ added in v0.0.2
func (g *GRPCGatewayClient) OnNotification(ctx context.Context, rec NotificationRecord) error
OnNotification forwards a mission-lifecycle notification to the gateway.
func (*GRPCGatewayClient) PostMessage ¶ added in v0.0.2
func (g *GRPCGatewayClient) PostMessage(ctx context.Context, req PostMessageRequest) error
PostMessage forwards a post request to the gateway.
type Gateway ¶
type Gateway interface {
// Configure runs once at gateway startup. Settings come from the
// HCL `settings = { ... }` map. The api handle is squadron's
// SquadronAPI implementation reachable over the broker stream.
//
// Implementations should perform their startup catch-up here
// (ListHumanInputs with a `Since` derived from local checkpoint).
Configure(ctx context.Context, settings map[string]string, api SquadronAPI) error
// OnHumanInputRequested is invoked once per new request that
// squadron observes. Gateways post the question to their external
// system and arrange for the user's reply to flow back via
// SquadronAPI.ResolveHumanInput.
OnHumanInputRequested(ctx context.Context, rec HumanInputRecord) error
// OnHumanInputResolved is invoked once per resolution event,
// regardless of who originated the resolution (this gateway, a
// commander operator, another gateway, the agent timing out).
// Gateways update their external surface to reflect the answer.
OnHumanInputResolved(ctx context.Context, rec HumanInputRecord) error
// OnNotification is invoked when a mission reaches a terminal state
// (completed, failed, stopped) and the mission opted into gateway
// notifications. Gateways post an informational message to their
// external system; there is nothing for the user to act on.
OnNotification(ctx context.Context, rec NotificationRecord) error
// PostMessage posts a message to the gateway's external system. The
// payload is the raw, gateway-schema-shaped JSON the agent produced for
// the builtins.gateway.post tool; the gateway parses it itself.
PostMessage(ctx context.Context, req PostMessageRequest) error
// MessageToolSpec returns the description + optional JSON Schema squadron
// uses to present the builtins.gateway.post tool to the LLM. Return a zero
// MessageToolSpec to accept squadron's default { message } shape.
MessageToolSpec(ctx context.Context) (MessageToolSpec, error)
// Shutdown is invoked once when squadron is tearing the subprocess
// down. Release external resources here (close the Discord
// session, flush queues, persist checkpoint).
Shutdown(ctx context.Context) error
}
Gateway is the contract every gateway implementation must satisfy. Squadron calls these methods over the gRPC subprocess channel.
The squadron-supplied SquadronAPI is delivered to the gateway via Configure. Implementations should hold onto it for the lifetime of the gateway and use it to pull state on startup (catch-up) and to write resolutions when the external system's user answers a request.
type HostPlugin ¶
type HostPlugin struct {
plugin.Plugin
// API is squadron's implementation of the SquadronAPI surface. The
// host plugin registers it on a broker stream and hands the stream
// id to the gateway during Configure so the gateway can dial back.
API SquadronAPI
}
HostPlugin is the variant of the gRPC plugin used by the host (squadron) to talk to a launched gateway subprocess. Construct it with the SquadronAPI implementation you want the gateway to call back into; the host side automatically registers that on a broker stream and passes the stream id to the gateway in Configure.
func (*HostPlugin) GRPCClient ¶
func (h *HostPlugin) GRPCClient(ctx context.Context, broker *plugin.GRPCBroker, c *grpc.ClientConn) (interface{}, error)
func (*HostPlugin) GRPCServer ¶
func (h *HostPlugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Server) error
type HumanInputFilter ¶
type HumanInputFilter struct {
// State narrows by lifecycle state. Empty means both open + resolved.
State HumanInputState
// MissionID restricts to a specific mission run.
MissionID string
// Since is the catch-up cursor: rows whose requested_at OR
// resolved_at is at or after this time. Use for reconnect / startup
// backfill — pass the latest event timestamp the gateway has
// already processed locally.
Since time.Time
// OldestFirst flips the default newest-first ordering. Use this
// when replaying for catch-up so events are processed in the order
// they happened.
OldestFirst bool
// Limit caps the page size. 0 means "use server default".
Limit int
// Offset skips that many results from the start of the page.
Offset int
}
HumanInputFilter narrows a ListHumanInputs call. Zero-valued fields are not applied (so a fresh HumanInputFilter{} returns everything).
type HumanInputRecord ¶
type HumanInputRecord struct {
ID string
MissionID string
MissionName string
TaskID string
TaskName string
ToolCallID string
Question string
ShortSummary string
AdditionalContext string
Choices []string
// MultiSelect is true when the human may pick 1+ choices rather
// than exactly one. When true, the resolved Response is a JSON-
// encoded string array (e.g. `["A","C"]`); when false, Response is
// the single chosen string. Always false for free-text questions.
MultiSelect bool
State HumanInputState
RequestedAt time.Time
ResolvedAt time.Time // zero when State is open
Response string
ResponderUserID string
}
HumanInputRecord is the canonical view of a single ask_human request, mirrored from squadron's store.
Timestamps are RFC3339Nano strings on the wire so the proto schema stays language-neutral, but the SDK exposes them as time.Time on the Go interface for convenience. Empty timestamps decode to the zero time.Time.
type HumanInputState ¶
type HumanInputState string
HumanInputState is the lifecycle state of an ask_human request.
const ( HumanInputStateOpen HumanInputState = "open" HumanInputStateResolved HumanInputState = "resolved" )
type MessageToolSpec ¶ added in v0.0.2
type MessageToolSpec struct {
// Description is appended to the tool description so the LLM knows how to
// format messages for this gateway.
Description string
// ParamsSchema is an optional JSON Schema (object) for the tool's
// parameters. Empty → squadron's default { message } shape.
ParamsSchema string
}
MessageToolSpec describes the builtins.gateway.post tool for one gateway.
type NotificationRecord ¶ added in v0.0.2
type NotificationRecord struct {
MissionID string
MissionName string
// Event is one of "mission_completed" or "mission_failed".
Event string
Title string
Message string
OccurredAt time.Time
// Error is set when Event is "mission_failed", empty otherwise.
Error string
// Channel is an optional per-mission destination override. When
// empty the gateway posts to its globally configured default channel.
Channel string
}
NotificationRecord describes a single mission-lifecycle notification pushed to the gateway. Notifications are one-way and informational — unlike human-input requests there is nothing for the user to resolve.
type PostMessageRequest ¶ added in v0.0.2
type PostMessageRequest struct {
Payload string
Attachments []FileAttachment
}
PostMessageRequest carries the raw, gateway-schema-shaped JSON the agent produced for the builtins.gateway.post tool (text, channel override, rich layout) plus any squadron-resolved file attachments. The gateway parses Payload and uploads each attachment's bytes directly — it never fetches a URL.
type ResolveResult ¶
type ResolveResult struct {
// Record is the canonical row after the call. For not-found this
// is the zero value.
Record HumanInputRecord
// AlreadyResolved is true when the request was resolved by some
// other actor before this call landed. Display "already answered
// by X" rather than confirming the gateway's user's submission.
AlreadyResolved bool
// NotFound is true when the tool_call_id is unknown to squadron.
// Most commonly: the row was trimmed from the store, or a stale
// event made it into the gateway's queue. Treat as a no-op.
NotFound bool
}
ResolveResult describes the outcome of a ResolveHumanInput call.
The two boolean flags exist because squadron treats both already-resolved and not-found as non-error outcomes — they're distinguishable expected results, not exceptions. Wrapping in an error type would force gateways to deal with errors.Is / errors.As for the common case of "I just want to know if my click landed".
type SquadronAPI ¶
type SquadronAPI interface {
ListHumanInputs(ctx context.Context, filter HumanInputFilter) ([]HumanInputRecord, int, error)
ResolveHumanInput(ctx context.Context, toolCallID, response, responderUserID string) (ResolveResult, error)
}
SquadronAPI is the squadron-side surface that gateways call into. The list deliberately starts small; new capabilities (mission management, dataset queries, cost reporting, …) are added here over time without breaking existing gateways.