mateclaw/mateclaw-server/src/main/java/vip/mate/goal/config/GoalProperties.java
matevip 1affbd7b82 feat(agent): loop-engineering robustness — goal continuation, plan re-plan, stall detection
- goal: continue (not skip) on max-iterations and evidence-insufficient turns.
  A max-iterations turn grants a fresh iteration budget ("hard continuation"),
  bounded per run and sized into the graph recursion ceiling, so a task too big
  for one budget keeps going instead of stalling until the next user message.
- plan-execute: re-plan the remaining work on a step exception, and on a
  signature-based stall (repeated failures / identical results / no usable
  result) instead of advancing dependent steps with junk; bounded by a per-run
  re-plan cap, with a graduated change-strategy nudge before the hard stop.
- plan-execute: auto-derive a goal from a genuine multi-step plan, seeding the
  acceptance criteria from the plan steps, so the goal subsystem engages without
  the model calling setGoal; broadcast goal_created so the UI hydrates.
- react: refund the iteration for setup-only rounds (load_skill / enable_tool)
  so a tight budget is not eaten by the load-then-use two-step.
- ui: re-fetch the active goal when a turn finishes so a goal created or mutated
  mid-conversation surfaces without depending on an SSE event.
- streaming: make retry backoff / total-time budget instance fields with a
  test-only seam; clarify that the wall-clock budget (not max-retries) bounds a
  sustained SERVER_ERROR loop to ~8 attempts, fixing the slow/flaky retry test.
2026-06-17 06:37:08 +08:00

107 lines
4.7 KiB
Java

package vip.mate.goal.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* Configuration knobs for the persistent-goal subsystem.
*
* <p>{@link #enabled} is the master gate: when {@code false} the StateGraph
* wiring stays inactive (no graph node touches the table), while
* {@code findActiveByConversation} still works for tests.
*/
@Data
@Component
@ConfigurationProperties(prefix = "mateclaw.goal")
public class GoalProperties {
/**
* Compile-time ceiling for {@link #maxHardContinuationsPerRun}. The graph
* recursion limit is sized statically to accommodate this many extra
* fresh-budget ReAct segments per run, so the runtime value is clamped to
* it — an operator cannot push hard continuations past what the recursion
* backstop was sized for. Raising this requires re-sizing the recursion
* ceiling in {@code AgentGraphBuilder.frameworkRecursionLimit()}.
*/
public static final int MAX_HARD_CONTINUATIONS_CEILING = 3;
/**
* Master switch — when off, the graph never invokes GoalEvaluationNode
* (the conditional edge sees no active goal, so the node is unreachable).
* Operators who want to disable goal evaluation entirely can override via
* {@code mateclaw.goal.enabled=false} in application.yml.
*/
private boolean enabled = true;
/**
* Create-time default for a goal's {@code autoFollowupEnabled} when the
* caller leaves it unspecified (null). Explicit true/false in the request
* is never overridden by this.
*/
private boolean defaultAutoFollowup = true;
/**
* Runtime hard gate for auto-followup. When false, no goal injects a
* follow-up regardless of its per-goal {@code autoFollowupEnabled} flag —
* the operator's kill switch for the self-continuation loop that takes
* effect immediately, even for goals created with the flag on.
*/
private boolean allowAutoFollowup = true;
/**
* Auto-derive a goal from a multi-step Plan-Execute plan. The Plan-Execute
* planner decomposes the request into steps and the step executor is a
* narrow "task runner" — neither calls {@code setGoal}, so without this a
* Plan-Execute run never engages the goal subsystem. When enabled, a goal is
* created server-side at plan generation (title = request, acceptance
* criteria seeded from the plan steps), so the already-wired
* GoalEvaluationNode tracks completion. Gated by {@link #enabled}; only
* fires for genuine multi-step plans and when the conversation has no active
* goal yet. Set to {@code false} to keep Plan-Execute goal-free.
*/
private boolean autoGoalFromPlan = true;
/** Default turn budget when the user doesn't override. */
private int defaultTurnBudget = 20;
/** Default combined (agent + eval) LLM call budget. */
private int defaultLlmCallBudget = 200;
/** Default cooldown between auto-followups in seconds. */
private int autoFollowupCooldownSeconds = 0;
/**
* Max auto-followups injected within a single graph run (one user turn).
* Caps the self-continuation loop so one message can't drive too many
* autonomous steps or approach the graph recursion limit. The goal's
* overall {@code turn_budget} still bounds total turns across messages;
* this is the tighter per-message safety net.
*/
private int maxFollowupsPerRun = 8;
/**
* Max "hard continuations" per single graph run. A hard continuation is a
* goal follow-up that re-enters the ReAct loop with a FRESH iteration
* budget after a turn that hit {@code MAX_ITERATIONS_REACHED} — letting a
* task too large for one budget keep going autonomously instead of stalling
* until the user sends another message. Each one costs up to a full
* {@code maxIterations} worth of node visits, so this is a dedicated cap on
* top of {@link #maxFollowupsPerRun}, clamped to
* {@link #MAX_HARD_CONTINUATIONS_CEILING} and sized into the graph recursion
* ceiling. The goal's cross-turn turn / LLM-call budgets still apply. Set to
* 0 to keep the previous behaviour (max-iterations turns end the run).
*/
private int maxHardContinuationsPerRun = 1;
/**
* Provider/model id for the evaluator. Empty string means "use the
* same model as the chat agent" — convenient for dev, expensive in
* production. Operators should point this at a cheap model.
*/
private String evaluatorModel = "";
/** Max messages from parent conversation included in evaluator prompt. */
private int evaluatorContextMessages = 8;
}