Drop agent-run lease and add approval resume
The worker leaned on a lease plus a heartbeat goroutine and a stale recovery sweep to reclaim runs from crashed workers. That machinery raced with long LLM and tool calls and conflated graceful stops with failures. Remove the lease columns, heartbeat, and stale recovery, and rely on FOR UPDATE SKIP LOCKED for single-claim plus explicit state transitions: a graceful suspend returns the run to PENDING and a crash now leaves it RUNNING for manual recovery. Treat an approval interruption as a known stop that parks the run in AWAITING_APPROVAL, and add SubmitApproval to merge human decisions into the checkpoint and requeue the run to PENDING. The decisions must cover exactly the pending approvals, since a missing one would resume as an implicit denial. Expose this through the submitAgentRunApproval mutation. Signed-off-by: Bryan Frimin <bryan@probo.com>
This commit is contained in:
@@ -12,6 +12,8 @@ import (
|
||||
|
||||
"github.com/vikstrous/dataloadgen"
|
||||
"go.gearno.de/kit/log"
|
||||
"go.probo.inc/probo/pkg/agent"
|
||||
"go.probo.inc/probo/pkg/agentrun"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"go.probo.inc/probo/pkg/probo"
|
||||
"go.probo.inc/probo/pkg/server/api/console/v1/dataloader"
|
||||
@@ -49,7 +51,7 @@ func (r *agentRunResolver) Permission(ctx context.Context, obj *types.AgentRun,
|
||||
|
||||
// TotalCount is the resolver for the totalCount field.
|
||||
func (r *agentRunConnectionResolver) TotalCount(ctx context.Context, obj *types.AgentRunConnection) (int, error) {
|
||||
scope, err := r.authorize(ctx, obj.ParentID, probo.ActionAgentRunList)
|
||||
scope, err := r.authorize(ctx, obj.ParentID, agentrun.ActionAgentRunList)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@@ -70,6 +72,46 @@ func (r *agentRunConnectionResolver) TotalCount(ctx context.Context, obj *types.
|
||||
return 0, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
// SubmitAgentRunApproval is the resolver for the submitAgentRunApproval field.
|
||||
func (r *mutationResolver) SubmitAgentRunApproval(ctx context.Context, input types.SubmitAgentRunApprovalInput) (*types.SubmitAgentRunApprovalPayload, error) {
|
||||
scope, err := r.authorize(ctx, input.AgentRunID, agentrun.ActionAgentRunApprove)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
decisions := make(map[string]agent.ApprovalResult, len(input.Decisions))
|
||||
for _, decision := range input.Decisions {
|
||||
message := ""
|
||||
if decision.Reason != nil {
|
||||
message = *decision.Reason
|
||||
}
|
||||
|
||||
decisions[decision.ToolCallID] = agent.ApprovalResult{
|
||||
Approved: decision.Approved,
|
||||
Message: message,
|
||||
}
|
||||
}
|
||||
|
||||
run, err := r.agentRun.SubmitApproval(ctx, scope, input.AgentRunID, decisions)
|
||||
if err != nil {
|
||||
switch {
|
||||
case errors.Is(err, agentrun.ErrAgentRunNotFound):
|
||||
return nil, gqlutils.NotFound(ctx, err)
|
||||
case errors.Is(err, agentrun.ErrNotAwaitingApproval):
|
||||
return nil, gqlutils.Conflictf(ctx, "agent run is not awaiting approval")
|
||||
case errors.Is(err, agentrun.ErrApprovalDecisionsMismatch):
|
||||
return nil, gqlutils.Invalidf(ctx, "approval decisions must match the run's pending approvals")
|
||||
default:
|
||||
r.logger.ErrorCtx(ctx, "cannot submit agent run approval", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
return &types.SubmitAgentRunApprovalPayload{
|
||||
AgentRun: types.NewAgentRun(run),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// AgentRun returns schema.AgentRunResolver implementation.
|
||||
func (r *Resolver) AgentRun() schema.AgentRunResolver { return &agentRunResolver{r} }
|
||||
|
||||
|
||||
@@ -58,3 +58,24 @@ type AgentRunEdge {
|
||||
cursor: CursorKey!
|
||||
node: AgentRun!
|
||||
}
|
||||
|
||||
extend type Mutation {
|
||||
submitAgentRunApproval(
|
||||
input: SubmitAgentRunApprovalInput!
|
||||
): SubmitAgentRunApprovalPayload!
|
||||
}
|
||||
|
||||
input AgentRunApprovalDecisionInput {
|
||||
toolCallId: String!
|
||||
approved: Boolean!
|
||||
reason: String
|
||||
}
|
||||
|
||||
input SubmitAgentRunApprovalInput {
|
||||
agentRunId: ID!
|
||||
decisions: [AgentRunApprovalDecisionInput!]!
|
||||
}
|
||||
|
||||
type SubmitAgentRunApprovalPayload {
|
||||
agentRun: AgentRun!
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user