fix(agent): fix agent hardAbortX
This commit is contained in:
parent
23d9b653f8
commit
bfa8c88565
5 changed files with 42 additions and 29 deletions
|
|
@ -48,24 +48,6 @@ func (al *AgentLoop) emitEvent(kind EventKind, meta EventMeta, payload any) {
|
|||
al.eventBus.Emit(evt)
|
||||
}
|
||||
|
||||
func (al *AgentLoop) hookAbortError(ts *turnState, stage string, decision HookDecision) error {
|
||||
reason := decision.Reason
|
||||
if reason == "" {
|
||||
reason = "hook requested turn abort"
|
||||
}
|
||||
|
||||
err := fmt.Errorf("hook aborted turn during %s: %s", stage, reason)
|
||||
al.emitEvent(
|
||||
EventKindError,
|
||||
ts.eventMeta("hooks", "turn.error"),
|
||||
ErrorPayload{
|
||||
Stage: "hook." + stage,
|
||||
Message: err.Error(),
|
||||
},
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
func (al *AgentLoop) logEvent(evt Event) {
|
||||
fields := map[string]any{
|
||||
"event_kind": evt.Kind.String(),
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ func (p *Pipeline) ExecuteTools(
|
|||
toolLoop:
|
||||
for i, tc := range normalizedToolCalls {
|
||||
if ts.hardAbortRequested() {
|
||||
exec.abortedByHardAbort = true
|
||||
return ToolControlBreak
|
||||
}
|
||||
|
||||
|
|
@ -288,10 +289,11 @@ toolLoop:
|
|||
}
|
||||
continue
|
||||
case HookActionAbortTurn:
|
||||
_ = ts.requestHardAbort()
|
||||
exec.abortedByHook = true
|
||||
return ToolControlBreak
|
||||
case HookActionHardAbort:
|
||||
_ = ts.requestHardAbort()
|
||||
exec.abortedByHardAbort = true
|
||||
return ToolControlBreak
|
||||
}
|
||||
}
|
||||
|
|
@ -426,6 +428,7 @@ toolLoop:
|
|||
toolDuration := time.Since(toolStart)
|
||||
|
||||
if ts.hardAbortRequested() {
|
||||
exec.abortedByHardAbort = true
|
||||
return ToolControlBreak
|
||||
}
|
||||
|
||||
|
|
@ -449,10 +452,11 @@ toolLoop:
|
|||
}
|
||||
}
|
||||
case HookActionAbortTurn:
|
||||
_ = ts.requestHardAbort()
|
||||
exec.abortedByHook = true
|
||||
return ToolControlBreak
|
||||
case HookActionHardAbort:
|
||||
_ = ts.requestHardAbort()
|
||||
exec.abortedByHardAbort = true
|
||||
return ToolControlBreak
|
||||
}
|
||||
}
|
||||
|
|
@ -633,6 +637,7 @@ toolLoop:
|
|||
"pending_count": len(exec.pendingMessages),
|
||||
"allResponsesHandled": exec.allResponsesHandled,
|
||||
})
|
||||
exec.allResponsesHandled = false
|
||||
return ToolControlContinue
|
||||
}
|
||||
|
||||
|
|
@ -644,6 +649,7 @@ toolLoop:
|
|||
"steering_count": len(steerMsgs),
|
||||
})
|
||||
exec.pendingMessages = append(exec.pendingMessages, steerMsgs...)
|
||||
exec.allResponsesHandled = false
|
||||
return ToolControlContinue
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -104,9 +104,11 @@ func (p *Pipeline) CallLLM(
|
|||
exec.llmOpts = llmReq.Options
|
||||
}
|
||||
case HookActionAbortTurn:
|
||||
return ControlBreak, al.hookAbortError(ts, "before_llm", decision)
|
||||
exec.abortedByHook = true
|
||||
return ControlBreak, nil
|
||||
case HookActionHardAbort:
|
||||
_ = ts.requestHardAbort()
|
||||
exec.abortedByHardAbort = true
|
||||
return ControlBreak, nil
|
||||
}
|
||||
}
|
||||
|
|
@ -191,6 +193,7 @@ func (p *Pipeline) CallLLM(
|
|||
}
|
||||
if ts.hardAbortRequested() && errors.Is(err, context.Canceled) {
|
||||
_ = ts.requestHardAbort()
|
||||
exec.abortedByHardAbort = true
|
||||
return ControlBreak, nil
|
||||
}
|
||||
|
||||
|
|
@ -364,9 +367,11 @@ func (p *Pipeline) CallLLM(
|
|||
exec.response = llmResp.Response
|
||||
}
|
||||
case HookActionAbortTurn:
|
||||
return ControlBreak, al.hookAbortError(ts, "after_llm", decision)
|
||||
exec.abortedByHook = true
|
||||
return ControlBreak, nil
|
||||
case HookActionHardAbort:
|
||||
_ = ts.requestHardAbort()
|
||||
exec.abortedByHardAbort = true
|
||||
return ControlBreak, nil
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -147,6 +147,9 @@ func (al *AgentLoop) runTurn(ctx context.Context, ts *turnState, pipeline *Pipel
|
|||
TotalContentLen: totalContentLen,
|
||||
},
|
||||
)
|
||||
// Clear exec.pendingMessages after injection so InitialSteeringMessages
|
||||
// are not re-injected on subsequent iterations (Issue 2 fix).
|
||||
exec.pendingMessages = nil
|
||||
}
|
||||
// Always sync messages into exec.messages so CallLLM sees the updated state
|
||||
exec.messages = messages
|
||||
|
|
@ -169,12 +172,20 @@ func (al *AgentLoop) runTurn(ctx context.Context, ts *turnState, pipeline *Pipel
|
|||
pendingMessages = exec.pendingMessages
|
||||
finalContent = exec.finalContent
|
||||
|
||||
logger.InfoCF("agent", "SWITCH ctrl", map[string]any{"ctrl": int(ctrl), "iter": iteration})
|
||||
switch ctrl {
|
||||
case ControlContinue:
|
||||
logger.InfoCF("agent", "CASE ControlContinue", map[string]any{"iter": iteration})
|
||||
continue
|
||||
case ControlBreak:
|
||||
// Hard abort: delegate to abortTurn (sets TurnEndStatusAborted)
|
||||
if exec.abortedByHardAbort {
|
||||
turnStatus = TurnEndStatusAborted
|
||||
return al.abortTurn(ts)
|
||||
}
|
||||
// Hook abort (HookActionAbortTurn): sets TurnEndStatusError, returns error
|
||||
if exec.abortedByHook {
|
||||
turnStatus = TurnEndStatusError
|
||||
return turnResult{}, fmt.Errorf("hook requested turn abort")
|
||||
}
|
||||
// Ensure empty response falls back to DefaultResponse
|
||||
if finalContent == "" {
|
||||
finalContent = ts.opts.DefaultResponse
|
||||
|
|
@ -190,17 +201,22 @@ func (al *AgentLoop) runTurn(ctx context.Context, ts *turnState, pipeline *Pipel
|
|||
messages = exec.messages
|
||||
continue
|
||||
case ToolControlBreak:
|
||||
// Hard abort: delegate to abortTurn (sets TurnEndStatusAborted)
|
||||
if exec.abortedByHardAbort {
|
||||
turnStatus = TurnEndStatusAborted
|
||||
return al.abortTurn(ts)
|
||||
}
|
||||
// Hook abort (HookActionAbortTurn): sets TurnEndStatusError, returns error
|
||||
if exec.abortedByHook {
|
||||
turnStatus = TurnEndStatusError
|
||||
return turnResult{}, fmt.Errorf("hook requested turn abort")
|
||||
}
|
||||
// ExecuteTools returned ControlBreak:
|
||||
// - allResponsesHandled=true: finalize without DefaultResponse (exec.finalContent empty)
|
||||
// - allResponsesHandled=false: coordinator applies DefaultResponse before finalize
|
||||
if exec.allResponsesHandled {
|
||||
finalContent = ""
|
||||
}
|
||||
// Check hard abort after tool execution (may have been set during ExecuteTools)
|
||||
if ts.hardAbortRequested() {
|
||||
turnStatus = TurnEndStatusAborted
|
||||
return al.abortTurn(ts)
|
||||
}
|
||||
return pipeline.Finalize(ctx, turnCtx, ts, exec, turnStatus, finalContent)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -141,6 +141,10 @@ type turnExecution struct {
|
|||
|
||||
// Phase tracking
|
||||
phase LLMPhase
|
||||
|
||||
// Abort signaling for coordinator (set by Pipeline methods)
|
||||
abortedByHardAbort bool // true when hard abort triggered during LLM/tools
|
||||
abortedByHook bool // true when HookActionAbortTurn triggered
|
||||
}
|
||||
|
||||
// newTurnExecution creates a turnExecution initialized from turnState and options.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue