federation

package
v0.1.2 Latest Latest
Warning

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

Go to latest
Published: Jul 31, 2026 License: Apache-2.0 Imports: 12 Imported by: 0

Documentation

Overview

Package federation implements the v0.1 slice of docs/federation-design.md: the frozen Adapter interface, reference parsing for `<scheme>://<name>[/<skill>]` remote-agent references, a scheme-keyed adapter registry, and the planner-facing `invoke_remote_agent` tool.

The frozen Adapter shape, and why

docs/fork-design.md P1.4 requires the interface be "frozen with an event/interrupt channel in mind — so v0.2's streaming/HITL propagation isn't a breaking change". The docs flagged signature churn as the risk, so the shape choice is load-bearing. Three candidate shapes were considered:

  1. Invoke returns (*Result, error) directly. Rejected: it hard-wires "the invocation is finished when Invoke returns". v0.2 streaming must deliver events BEFORE the terminal result exists, so this shape forces either a signature change or a semantics change (Invoke returning a half-populated Result) — exactly the churn we're freezing against.

  2. Invoke takes a callback/event-sink field on InvokeOptions. Rejected: adding the field later is non-breaking structurally, but it inverts the data flow (push into caller-supplied sink vs. pull from the invocation) and gives interrupts/HITL no natural home — a remote `input-required` pause needs a bidirectional handle, not a write-only sink.

  3. Invoke returns a Handle (chosen). The Handle carries the whole post-dispatch lifecycle: Wait for the terminal Result, Events for intermediate updates, Cancel for remote cancellation. v0.1 adapters do all work synchronously inside Invoke (v0.1 blocks to a bounded timeout, per docs/durable-execution-design.md phasing) and return an already-resolved Handle; v0.2 adapters return a live Handle whose Events stream and whose Wait blocks. Callers written against Wait/Events today keep compiling AND keep their semantics — only WHERE the waiting happens moves. Interrupt propagation (v0.2 HITL) lands as a new Event type plus a response method on a Handle extension interface, not as a signature edit.

Contract split: Invoke returns a non-nil error only for dispatch-time failures (unresolvable reference, unknown agent, invalid options). Execution failures — transport errors, remote task failure, timeout — surface from Handle.Wait, so callers have exactly one place to handle them regardless of whether the adapter is synchronous or streaming.

This shape freezes at v0.1. Additive evolution only: new fields on InvokeOptions/Result/Event structs, new Event types, extension interfaces on Handle.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrUnknownScheme — no adapter registered for the reference scheme.
	ErrUnknownScheme = errors.New("federation: unknown reference scheme")

	// ErrUnknownAgent — the scheme resolved but the named agent is not
	// configured (e.g. no matching .agents/a2a/*.yaml).
	ErrUnknownAgent = errors.New("federation: unknown remote agent")

	// ErrInvalidReference — the reference string does not parse against
	// the `<scheme>://<name>[/<skill>]` grammar.
	ErrInvalidReference = errors.New("federation: invalid reference")

	// ErrUnreachable — network partition, DNS failure, endpoint down.
	ErrUnreachable = errors.New("federation: remote agent unreachable")

	// ErrTimeout — the bounded v0.1 wait expired before the remote
	// reached a terminal state.
	ErrTimeout = errors.New("federation: remote invocation timed out")

	// ErrAuthFailed — the remote rejected our credentials (401/403) or
	// the configured credential source is unusable.
	ErrAuthFailed = errors.New("federation: remote authentication failed")

	// ErrProtocolMismatch — the remote does not speak a transport this
	// adapter supports (e.g. an agent card advertising only gRPC).
	ErrProtocolMismatch = errors.New("federation: protocol mismatch")

	// ErrRemoteFailed — the remote agent reached a non-success terminal
	// state (failed / rejected / canceled remotely).
	ErrRemoteFailed = errors.New("federation: remote invocation failed")
)

Sentinel errors, named per the docs/federation-design.md failure-mode table. Adapters wrap these so callers can errors.Is across protocols.

Functions

func NewInvokeRemoteAgentTool

func NewInvokeRemoteAgentTool(reg *Registry) (tool.Tool, error)

NewInvokeRemoteAgentTool builds the single unified planner tool `invoke_remote_agent(reference, inputs)` over a Registry. cmd/mast (or a library consumer) constructs the Registry with the adapters the deployment permits and adds the returned tool to the planner's tool list; this package does no global registration.

v0.1 semantics: the call blocks to a bounded timeout (the remote agent config's timeout; docs/a2a-design.md v0.1 phasing row) and the tool result carries only the terminal state — intermediate events and HITL propagation arrive with v0.2 per the frozen Handle contract.

Types

type Adapter

type Adapter interface {
	// Scheme returns the reference scheme this adapter serves ("a2a",
	// "mast", "http", ...). Must be non-empty, lowercase, and stable.
	Scheme() string

	// Invoke dispatches inputs to the remote agent identified by ref.
	// It returns an error only for dispatch-time failures (resolution,
	// validation); execution errors surface from Handle.Wait. v0.1
	// adapters MAY complete the entire remote interaction inside Invoke
	// (bounded by ctx and opts.Timeout) and return a resolved Handle.
	Invoke(ctx context.Context, ref Reference, inputs map[string]any, opts InvokeOptions) (Handle, error)
}

Adapter is the protocol extension point from docs/federation-design.md. One adapter per reference scheme; the registry dispatches on Reference.Scheme. FROZEN at v0.1 — see the package documentation for the compatibility contract.

type Event

type Event struct {
	// Type discriminates the event ("status", "artifact",
	// "input-required", ...). Namespaced growth, additive-only.
	Type string

	// Data is the type-specific payload.
	Data map[string]any
}

Event is one intermediate update from a remote invocation. v0.1 defines the envelope only — no adapter produces events yet. v0.2 (streaming, HITL propagation) adds Type values; consumers must ignore Types they do not recognize.

type Handle

type Handle interface {
	// Wait blocks until the invocation reaches a terminal state and
	// returns the Result, or the terminal error. Wait is idempotent:
	// subsequent calls return the same outcome. ctx bounds the wait
	// only; canceling it does not by itself cancel the remote task
	// (use Cancel).
	Wait(ctx context.Context) (*Result, error)

	// Events returns the intermediate-event stream. v0.1 adapters
	// return an already-closed channel (no events precede the terminal
	// state in synchronous mode); v0.2 streaming adapters deliver task
	// updates here. The channel is closed when the invocation reaches a
	// terminal state. Never returns nil.
	Events() <-chan Event

	// Cancel requests cancellation of the remote work. Idempotent;
	// best-effort. After a successful Cancel, Wait returns an error
	// wrapping ErrRemoteFailed (or the remote's terminal outcome if it
	// won the race).
	Cancel(ctx context.Context) error
}

Handle is the post-dispatch lifecycle of one remote invocation.

func NewResolvedHandle

func NewResolvedHandle(res *Result, err error) Handle

NewResolvedHandle wraps an already-terminal outcome in a Handle. It is the intended return path for v0.1 synchronous adapters (and for tests). Exactly one of res/err is meaningful; err wins when non-nil.

type InvokeOptions

type InvokeOptions struct {
	// Timeout bounds the whole invocation (dispatch through terminal
	// state). Zero means the adapter's / remote-agent config's default.
	Timeout time.Duration
}

InvokeOptions carries per-invocation knobs. A zero value is valid. Growth is additive-only (new fields, never changed ones).

type InvokeRemoteAgentArgs

type InvokeRemoteAgentArgs struct {
	// Reference identifies the remote agent:
	// <scheme>://<name>[/<skill>], e.g. a2a://external-triage/investigate-incident.
	Reference string `json:"reference"`

	// Inputs is the structured input payload for the remote agent.
	Inputs map[string]any `json:"inputs,omitempty"`
}

InvokeRemoteAgentArgs is the LLM-facing argument schema for the invoke_remote_agent planner tool (docs/federation-design.md: "the planner's tool vocabulary gains one class").

type Reference

type Reference struct {
	// Scheme selects the adapter ("a2a", and in later versions "mast",
	// "http", "grpc"). Always lowercase.
	Scheme string

	// Name is the configured remote-agent name (URI host position).
	// Always lowercase.
	Name string

	// Skill is the optional skill selector. Empty means "the agent's
	// default / sole skill".
	Skill string

	// Raw is the reference string as given.
	Raw string
}

Reference is a parsed remote-agent reference per the docs/federation-design.md grammar. v0.1 accepts both spellings the design corpus uses for skill selection:

<scheme>://<name>/<skill>     (docs/a2a-design.md)
<scheme>://<name>?skill=<s>   (docs/federation-design.md)

Supplying both is an error rather than a silent precedence rule. Parsing is standard-URI (docs/federation-design.md open question 1's bias: "use standard URI parsing; scheme becomes the adapter selector"), which means the name occupies the URI host position and is therefore case-insensitive per RFC 3986 — Parse normalizes it to lowercase, and agent config names must be lowercase to match (the pkg/a2a config loader enforces this). ParseReference applies the lowercase normalization itself — net/url preserves host case on parse, but two references differing only in name case MUST resolve identically, so normalization happens here, once.

func ParseReference

func ParseReference(raw string) (Reference, error)

ParseReference parses a remote-agent reference. All parse failures wrap ErrInvalidReference.

func (Reference) String

func (r Reference) String() string

String returns the canonical `<scheme>://<name>[/<skill>]` form.

type Registry

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

Registry maps reference schemes to Adapters and dispatches invocations. Safe for concurrent use.

func NewRegistry

func NewRegistry(adapters ...Adapter) *Registry

NewRegistry returns a Registry with the given adapters registered. It panics on the same conditions Register rejects — construction with a duplicate or invalid scheme is a programming error, not a runtime condition.

func (*Registry) Adapter

func (r *Registry) Adapter(scheme string) (Adapter, error)

Adapter returns the adapter registered for scheme, or an error wrapping ErrUnknownScheme.

func (*Registry) Invoke

func (r *Registry) Invoke(ctx context.Context, raw string, inputs map[string]any, opts InvokeOptions) (Handle, error)

Invoke parses raw, resolves the adapter by scheme, and dispatches. Same error contract as Adapter.Invoke: dispatch-time failures here, execution failures from Handle.Wait.

func (*Registry) Register

func (r *Registry) Register(a Adapter) error

Register adds an adapter, keyed by its Scheme. Duplicate or empty schemes are rejected.

type Result

type Result struct {
	// State is the terminal state as reported by the remote, normalized
	// to the remote protocol's vocabulary (A2A: "completed"; a direct
	// message reply also reports "completed").
	State string

	// RemoteID identifies the remote unit of work when the protocol has
	// one (A2A task ID). Empty for direct request/response replies.
	RemoteID string

	// Text is the concatenated human-readable output (A2A text parts).
	Text string

	// Output is the structured output (A2A data parts, merged in
	// arrival order — later keys win).
	Output map[string]any

	// Raw is the protocol-level terminal payload, for debugging and for
	// callers that need fields Result does not model.
	Raw json.RawMessage
}

Result is the terminal outcome of a remote invocation, protocol agnostic. Growth is additive-only.

Jump to

Keyboard shortcuts

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