| 1 | package agent |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "errors" |
| 6 | "fmt" |
| 7 | |
| 8 | "reasonix/internal/provider" |
| 9 | ) |
| 10 | |
| 11 | // maxStepsPause is a resumable stop after a positive model-round budget. |
| 12 | type maxStepsPause struct { |
| 13 | steps int |
| 14 | key string |
| 15 | } |
| 16 | |
| 17 | func (e *maxStepsPause) Error() string { |
| 18 | return fmt.Sprintf("paused after %d tool-call rounds (%s) — the work so far is saved; send another message to continue, or set %s higher or to 0 for no limit", e.steps, e.key, e.key) |
| 19 | } |
| 20 | |
| 21 | func isToolLoopPause(err error) bool { |
| 22 | var maxPause *maxStepsPause |
| 23 | var budgetPause *taskBudgetPause |
| 24 | return errors.As(err, &maxPause) || errors.As(err, &budgetPause) |
| 25 | } |
| 26 | |
| 27 | // HostProgressSignatures exposes successful evidence identities to the Goal FSM. |
| 28 | func (a *Agent) HostProgressSignatures() []string { |
| 29 | if a == nil || a.task.ledger == nil { |
| 30 | return nil |
| 31 | } |
| 32 | return a.task.ledger.SuccessfulProgressSignaturesSince(0) |
| 33 | } |
| 34 | |
| 35 | // resetTurnEvidence starts a fresh execution-fact projection for a new task |
| 36 | // scope. Facts remain useful for display and Goal accounting but do not gate |
| 37 | // later tools or model completion. |
| 38 | func (a *Agent) resetTurnEvidence() { |
| 39 | a.task.restartLedger() |
| 40 | } |
| 41 | |
| 42 | func (a *Agent) stopUnexecutedBoundaryCalls(ctx context.Context, state *turnRuntime, calls []provider.ToolCall, usage *provider.Usage) (error, bool) { |
| 43 | switch { |
| 44 | case state.graceRound && !a.allowsBoundaryTurnFinalizer(ctx, state, calls): |
| 45 | if err := a.pairUnexecutedGraceCalls(ctx, calls, "blocked: the tool-call round budget is exhausted; no more tools will run in this turn"); err != nil { |
| 46 | return err, true |
| 47 | } |
| 48 | return a.gracePause(state), true |
| 49 | default: |
| 50 | return nil, false |
| 51 | } |
| 52 | } |
| 53 |