Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions sdk/go/agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,11 @@ type Agent struct {
cancelMu sync.Mutex
cancelFuncs map[string]context.CancelFunc

// pauseManager tracks pending Agent.Pause() calls, keyed by
// approval_request_id, and resolves them when the control plane POSTs an
// approval resolution to /webhooks/approval.
pauseManager *PauseManager

startTime time.Time
}

Expand Down Expand Up @@ -576,6 +581,7 @@ func New(cfg Config) (*Agent, error) {
logger: cfg.Logger,
realtimeValidationFunctions: make(map[string]struct{}),
cancelFuncs: make(map[string]context.CancelFunc),
pauseManager: NewPauseManager(),
startTime: time.Now(),
}

Expand Down Expand Up @@ -821,6 +827,7 @@ func (a *Agent) handler() http.Handler {
mux.HandleFunc("/reasoners/", a.handleReasoner)
mux.HandleFunc("/skills/", a.handleSkill)
mux.HandleFunc("/_internal/executions/", a.handleInternalCancel)
mux.HandleFunc("/webhooks/approval", a.handleApprovalWebhook)

var handler http.Handler = mux

Expand Down Expand Up @@ -852,6 +859,15 @@ func (a *Agent) originAuthMiddleware(next http.Handler, token string) http.Handl
next.ServeHTTP(w, r)
return
}
// /webhooks/approval is a control-plane→worker approval-resolution
// callback delivered unauthenticated (see notifyApprovalCallback in the
// control plane), analogous to the cancel notification. It only
// resolves a pause the agent itself initiated, so treat it as an open
// infrastructure route rather than a caller-initiated invocation.
if path == "/webhooks/approval" {
next.ServeHTTP(w, r)
return
}

expected := "Bearer " + token
if r.Header.Get("Authorization") != expected {
Expand Down Expand Up @@ -883,6 +899,14 @@ func (a *Agent) localVerificationMiddleware(next http.Handler) http.Handler {
next.ServeHTTP(w, r)
return
}
// /webhooks/approval is a control-plane→worker approval-resolution
// callback, delivered unauthenticated by the control plane. It carries
// no DID signature; skip local verification (same rationale as the
// cancel notification above).
if path == "/webhooks/approval" {
next.ServeHTTP(w, r)
return
}

// Extract function name to check realtime validation requirement
funcName := ""
Expand Down
6 changes: 6 additions & 0 deletions sdk/go/agent/agent_lifecycle.go
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,12 @@ func (a *Agent) shutdown(ctx context.Context) error {
close(a.stopLease)
}

// Unblock any reasoner still parked in Agent.Pause() so shutdown does not
// hang waiting on an approval callback that will never arrive.
if a.pauseManager != nil {
a.pauseManager.CancelAll()
}

if a.client != nil {
if _, err := a.client.Shutdown(ctx, a.cfg.NodeID, types.ShutdownRequest{Reason: "shutdown", Version: a.cfg.Version}); err != nil {
a.logger.Printf("failed to notify shutdown: %v", err)
Expand Down
Loading
Loading