Documentation
¶
Overview ¶
Package compose hosts the agent runtime's graph-level primitives that sit just above the eino adapter — primitives the rest of pkg/agent composes against without ever importing eino types directly. Phase E adds the approval gate: a node-shaped primitive that suspends a run to wait on a human decision delivered by CLI, web UI, or MCP.
The approval gate is structurally simple: a Gate ties an Approver (the sandbox-side interface) to a persist.Recorder (the run's JSONL ledger) and a Notifier (the channel that surfaces the request). Each approval gets a fresh, namespaced ID; pending gates live in a process-wide Registry so an out-of-band consumer (CLI subcommand, HTTP handler, MCP notification) can resolve them by ID without holding a reference to the running graph.
Index ¶
Constants ¶
const DefaultApprovalTimeout = 24 * time.Hour
DefaultApprovalTimeout is the wall-clock window the runtime waits for an approval response before auto-rejecting. 24h is the design's quoted default; operators can override per-call via Gate.WithTimeout.
const DefaultApprovalWarnFraction = 0.8
DefaultApprovalWarnFraction is the fraction of DefaultApprovalTimeout at which a "still pending" warning is emitted. 0.8 means the warning fires after 19h12m for the default 24h window.
Variables ¶
var ErrAlreadyResolved = errors.New("compose: approval already resolved")
ErrAlreadyResolved is returned by Resolve when the same ID has been resolved already. Distinguished from ErrApprovalNotFound so callers can render different UX (404 vs 409 in HTTP, "already approved" vs "no such approval" in CLI).
var ErrApprovalNotFound = errors.New("compose: approval not found or already resolved")
ErrApprovalNotFound is returned by Resolve when the ID does not match a registered pending approval. Callers map this to HTTP 404 / CLI exit-2.
Functions ¶
This section is empty.
Types ¶
type ApprovalRequest ¶
type ApprovalRequest struct {
// RunID is the run that suspended on the gate.
RunID string
// ApprovalID is the handle a consumer responds against.
ApprovalID string
// Prompt is the human-readable description of what's being
// approved.
Prompt string
// Skill is the unprefixed skill name the run is executing under,
// when known. Empty for ad-hoc graph compositions.
Skill string
// Timeout is the wall-clock window before auto-rejection.
Timeout time.Duration
// CreatedAt is the wall-clock time the request was raised.
CreatedAt time.Time
}
ApprovalRequest carries the data a Notifier renders when an approval gate fires. The shape mirrors persist.ApprovalRequestPayload but carries the run ID and skill explicitly so a notifier can render a CLI banner or web push without a second JSONL read.
type Gate ¶
type Gate struct {
// contains filtered or unexported fields
}
Gate is the runtime-side approval primitive. One Gate is created per run — its RunID anchors every event and notification it emits. Gate satisfies sandbox.Approver, so it drops in wherever the TS sandbox expects an Approver and gives the run author a single `approval(prompt)` call site that surfaces simultaneously to the CLI, the web UI, and any MCP consumer.
func NewGate ¶
func NewGate(runID string, recorder *persist.Recorder, registry *Registry, notifier Notifier, opts ...GateOption) (*Gate, error)
NewGate constructs a Gate for a run. The recorder MUST be the run's JSONL writer; nil is rejected because the gate is not safe to use without persistence (a crashed approval would leave the CLI/web/MCP consumers unable to discover it on restart). Pass a SlogNotifier via WithNotifier to keep the operator-side audit line when no MCP/web notifier is wired.
type GateOption ¶
type GateOption func(*Gate)
GateOption configures a Gate at construction time.
func WithLogger ¶
func WithLogger(logger *slog.Logger) GateOption
WithLogger sets the slog.Logger the gate uses for its warn-at-80% line. Nil falls back to slog.Default at warn time.
func WithSkill ¶
func WithSkill(name string) GateOption
WithSkill records the skill name on the request so notifiers can render it without a second JSONL read.
func WithTimeout ¶
func WithTimeout(d time.Duration) GateOption
WithTimeout overrides DefaultApprovalTimeout. A non-positive value is silently dropped so callers can pass through a config field that may be unset.
func WithTimeoutPolicy ¶
func WithTimeoutPolicy(p TimeoutPolicy) GateOption
WithTimeoutPolicy overrides the default TimeoutReject behaviour.
func WithWarnFraction ¶
func WithWarnFraction(f float64) GateOption
WithWarnFraction overrides DefaultApprovalWarnFraction. Values outside (0, 1) are clamped to the default to avoid pathological no-warning or warn-immediately behaviour.
type MultiNotifier ¶
type MultiNotifier struct {
Notifiers []Notifier
}
MultiNotifier fans an ApprovalRequest out to several notifiers in order. A non-nil error from any notifier is returned and stops the fan-out; in practice operators wire SlogNotifier first so the audit line lands even if a downstream notifier (MCP, web push) fails.
func (*MultiNotifier) NotifyApproval ¶
func (m *MultiNotifier) NotifyApproval(ctx context.Context, req ApprovalRequest) error
NotifyApproval implements Notifier.
type Notifier ¶
type Notifier interface {
NotifyApproval(ctx context.Context, req ApprovalRequest) error
}
Notifier surfaces an ApprovalRequest to consumers (CLI banners, web UI banners, MCP notifications). Implementations MUST be non-blocking and idempotent — the gate fans out a single Notify call per request, then returns control to the run loop while it blocks on the decision channel.
type Pending ¶
type Pending struct {
ID string
RunID string
Skill string
Prompt string
CreatedAt time.Time
Timeout time.Duration
// contains filtered or unexported fields
}
Pending is the registry-side view of an in-flight approval. List returns slices of these for `runs list --format json` and for the web UI's polling endpoint; the Resolve method on the registry drains the corresponding channel once.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry is the process-wide pending-approvals table. Goroutines inside a Gate.Approve register a Pending; CLI / API / MCP consumers look up by ID and Resolve. Both sides are fully thread-safe.
func (*Registry) List ¶
List returns every currently pending approval. The slice is a snapshot; subsequent registrations or resolutions do not mutate the returned slice.
func (*Registry) Lookup ¶
Lookup returns a snapshot of a Pending by ID. The returned struct is safe to share — no mutable channel reference is exposed.
type SlogNotifier ¶
type SlogNotifier struct {
// Logger receives the warning line. nil falls back to
// slog.Default at call time.
Logger *slog.Logger
}
SlogNotifier is the default Notifier: it emits a structured warning log line each time an approval gate fires. The CLI banner and web UI banner are surfaced separately by the API/CLI layers; the slog line is the operator-side audit trail.
func (*SlogNotifier) NotifyApproval ¶
func (n *SlogNotifier) NotifyApproval(_ context.Context, req ApprovalRequest) error
NotifyApproval implements Notifier.
type TimeoutPolicy ¶
type TimeoutPolicy int
TimeoutPolicy describes what happens when an approval window elapses without a response. The runtime always emits a "warn at 80%" log line first; the policy controls only the terminal action.
const ( // TimeoutReject auto-rejects the request when the window // elapses. Default — matches the prompt's "approval gate timeout // transitions run to error" guidance. TimeoutReject TimeoutPolicy = iota // TimeoutBlock keeps the gate open indefinitely. Useful for // long-running interactive runs where the human is the // scheduling constraint. TimeoutBlock )