feat(config): relocate orchestration loop/threshold/budget constants to [orchestration] config
The kernel's ReAct-loop tuning constants (max tool rounds, read/rejection-loop nudge thresholds, feedback issue cap, repo-map top-k/files-per-dir, docs catalog cap, clarification-round cap, review-block confidence/retry cap, default refinement, recovery/intent route budgets) were hardcoded in SessionOrchestrator/ DefaultSessionOrchestrator. Relocated to a new OrchestrationTuning value threaded through the orchestrator constructor, mapped from CorrexConfig.orchestration in Main.kt, parsed in ConfigLoader, written by CorrexConfigWriter. Defaults equal the former constants so an absent [orchestration] section reproduces prior behavior. Startup-load (not hot-reload); pure output-truncation caps left as constants. Vikunja #46 (task 76).
This commit is contained in:
@@ -630,6 +630,19 @@ object ConfigLoader {
|
||||
compressionLevel = asInt(orchestrationSection["compression_level"], 4),
|
||||
tokenPrunerUrl =
|
||||
asString(orchestrationSection["token_pruner_url"], "http://127.0.0.1:8199"),
|
||||
maxToolRounds = asInt(orchestrationSection["max_tool_rounds"], 30),
|
||||
readLoopNudgeThreshold = asInt(orchestrationSection["read_loop_nudge_threshold"], 3),
|
||||
rejectionLoopNudgeThreshold = asInt(orchestrationSection["rejection_loop_nudge_threshold"], 3),
|
||||
maxFeedbackIssues = asInt(orchestrationSection["max_feedback_issues"], 3),
|
||||
repoMapInjectTopK = asInt(orchestrationSection["repo_map_inject_top_k"], 30),
|
||||
repoMapFilesPerDir = asInt(orchestrationSection["repo_map_files_per_dir"], 8),
|
||||
docsCatalogMax = asInt(orchestrationSection["docs_catalog_max"], 20),
|
||||
maxClarificationRounds = asInt(orchestrationSection["max_clarification_rounds"], 3),
|
||||
reviewBlockMinConfidence = asDouble(orchestrationSection["review_block_min_confidence"], 0.7),
|
||||
reviewBlockRetryCap = asInt(orchestrationSection["review_block_retry_cap"], 20),
|
||||
defaultMaxRefinement = asInt(orchestrationSection["default_max_refinement"], 3),
|
||||
recoveryRouteBudget = asInt(orchestrationSection["recovery_route_budget"], 2),
|
||||
intentRouteBudget = asInt(orchestrationSection["intent_route_budget"], 2),
|
||||
)
|
||||
|
||||
val modelsSettings = ModelsSettings(
|
||||
|
||||
@@ -69,6 +69,25 @@ data class OrchestrationKnobs(
|
||||
*/
|
||||
val compressionLevel: Int = 4,
|
||||
val tokenPrunerUrl: String = "http://127.0.0.1:8199",
|
||||
/**
|
||||
* Orchestration loop/threshold/budget tuning, read once at server startup. Defaults equal the
|
||||
* kernel's former hardcoded constants — an absent section reproduces prior behavior. Reach for
|
||||
* these when a local model misbehaves (thrashes a read loop, re-asks clarifications, gets trapped
|
||||
* by a stubborn reviewer). Mirrors kernel OrchestrationTuning.
|
||||
*/
|
||||
val maxToolRounds: Int = 30,
|
||||
val readLoopNudgeThreshold: Int = 3,
|
||||
val rejectionLoopNudgeThreshold: Int = 3,
|
||||
val maxFeedbackIssues: Int = 3,
|
||||
val repoMapInjectTopK: Int = 30,
|
||||
val repoMapFilesPerDir: Int = 8,
|
||||
val docsCatalogMax: Int = 20,
|
||||
val maxClarificationRounds: Int = 3,
|
||||
val reviewBlockMinConfidence: Double = 0.7,
|
||||
val reviewBlockRetryCap: Int = 20,
|
||||
val defaultMaxRefinement: Int = 3,
|
||||
val recoveryRouteBudget: Int = 2,
|
||||
val intentRouteBudget: Int = 2,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
|
||||
@@ -86,6 +86,21 @@ object CorrexConfigWriter {
|
||||
b.kv("stage_timeout_ms", cfg.orchestration.stageTimeoutMs)
|
||||
b.kv("journal_compaction_token_threshold", cfg.orchestration.journalCompactionTokenThreshold)
|
||||
b.kv("resume_abandoned_max_age_minutes", cfg.orchestration.resumeAbandonedMaxAgeMinutes)
|
||||
b.kv("compression_level", cfg.orchestration.compressionLevel)
|
||||
b.kv("token_pruner_url", cfg.orchestration.tokenPrunerUrl)
|
||||
b.kv("max_tool_rounds", cfg.orchestration.maxToolRounds)
|
||||
b.kv("read_loop_nudge_threshold", cfg.orchestration.readLoopNudgeThreshold)
|
||||
b.kv("rejection_loop_nudge_threshold", cfg.orchestration.rejectionLoopNudgeThreshold)
|
||||
b.kv("max_feedback_issues", cfg.orchestration.maxFeedbackIssues)
|
||||
b.kv("repo_map_inject_top_k", cfg.orchestration.repoMapInjectTopK)
|
||||
b.kv("repo_map_files_per_dir", cfg.orchestration.repoMapFilesPerDir)
|
||||
b.kv("docs_catalog_max", cfg.orchestration.docsCatalogMax)
|
||||
b.kv("max_clarification_rounds", cfg.orchestration.maxClarificationRounds)
|
||||
b.kv("review_block_min_confidence", cfg.orchestration.reviewBlockMinConfidence)
|
||||
b.kv("review_block_retry_cap", cfg.orchestration.reviewBlockRetryCap)
|
||||
b.kv("default_max_refinement", cfg.orchestration.defaultMaxRefinement)
|
||||
b.kv("recovery_route_budget", cfg.orchestration.recoveryRouteBudget)
|
||||
b.kv("intent_route_budget", cfg.orchestration.intentRouteBudget)
|
||||
|
||||
b.section("personalization")
|
||||
b.kv("enabled", cfg.personalization.enabled)
|
||||
|
||||
+8
-11
@@ -57,18 +57,14 @@ import java.util.concurrent.atomic.*
|
||||
)
|
||||
private val log = LoggerFactory.getLogger(DefaultSessionOrchestrator::class.java)
|
||||
|
||||
// Fallback refinement-loop cap when a stage does not declare maxRetries.
|
||||
private const val DEFAULT_MAX_REFINEMENT = 3
|
||||
|
||||
// Own, small budget for the retry-agency recovery route: how many times a single stage may be
|
||||
// routed to a recovery stage before the run fails terminally. Deliberately smaller than the
|
||||
// per-gate retry budget — recovery is a purpose-built repair-then-reverify loop, not open-ended.
|
||||
private const val RECOVERY_ROUTE_BUDGET = 2
|
||||
// RECOVERY_ROUTE_BUDGET / INTENT_ROUTE_BUDGET now in OrchestrationTuning.
|
||||
|
||||
// Tier-2 escalation budget: how many NO-PROGRESS routes the arbiter (recovery-stage-as-intent-holder)
|
||||
// gets after the tier-1 owner loop exhausts, before the run fails terminally. Independent of the owner
|
||||
// budget (keyed with INTENT_BUDGET_SUFFIX) so escalation is a genuine second chance, not a shared pool.
|
||||
private const val INTENT_ROUTE_BUDGET = 2
|
||||
|
||||
// Transition ids for the failure-ticket loop. A ticket routes the failing gate's control to the
|
||||
// repair owner (TICKET_ROUTE); the owner, once done, hands control straight back to the gate that
|
||||
@@ -109,7 +105,8 @@ class DefaultSessionOrchestrator(
|
||||
repoKnowledgeRetriever: RepoKnowledgeRetriever? = null,
|
||||
readyTaskCounter: ReadyTaskCounter? = null,
|
||||
taskClaimCoordinator: TaskClaimCoordinator? = null,
|
||||
) : SessionOrchestrator(repositories, engines, artifactStore, decisionJournalRepository, artifactKindRegistry = artifactKindRegistry, repoKnowledgeRetriever = repoKnowledgeRetriever, readyTaskCounter = readyTaskCounter, taskClaimCoordinator = taskClaimCoordinator), ApprovalGateway {
|
||||
tuning: OrchestrationTuning = OrchestrationTuning(),
|
||||
) : SessionOrchestrator(repositories, engines, artifactStore, decisionJournalRepository, artifactKindRegistry = artifactKindRegistry, repoKnowledgeRetriever = repoKnowledgeRetriever, readyTaskCounter = readyTaskCounter, taskClaimCoordinator = taskClaimCoordinator, tuning = tuning), ApprovalGateway {
|
||||
override val tokenizer: Tokenizer? = tokenizer
|
||||
override val cancellations: ConcurrentHashMap<SessionId, AtomicBoolean> =
|
||||
ConcurrentHashMap<SessionId, AtomicBoolean>()
|
||||
@@ -340,10 +337,10 @@ class DefaultSessionOrchestrator(
|
||||
// (target, budgetKey, budget, escalated). Prefer the owner tier while it has budget; escalate to
|
||||
// the arbiter tier once the owner loop is spent; null = no tier available now.
|
||||
val route = when {
|
||||
owner != null && !budgetExhausted(state, ownerKey, fingerprint, RECOVERY_ROUTE_BUDGET) ->
|
||||
RouteTier(owner, ownerKey, RECOVERY_ROUTE_BUDGET, escalated = false)
|
||||
arbiter != null && !budgetExhausted(state, intentKey, fingerprint, INTENT_ROUTE_BUDGET) ->
|
||||
RouteTier(arbiter, intentKey, INTENT_ROUTE_BUDGET, escalated = true)
|
||||
owner != null && !budgetExhausted(state, ownerKey, fingerprint, tuning.recoveryRouteBudget) ->
|
||||
RouteTier(owner, ownerKey, tuning.recoveryRouteBudget, escalated = false)
|
||||
arbiter != null && !budgetExhausted(state, intentKey, fingerprint, tuning.intentRouteBudget) ->
|
||||
RouteTier(arbiter, intentKey, tuning.intentRouteBudget, escalated = true)
|
||||
else -> null
|
||||
}
|
||||
if (route == null) {
|
||||
@@ -621,7 +618,7 @@ class DefaultSessionOrchestrator(
|
||||
// terminal failure instead of looping forever.
|
||||
if (isBackEdge(repositories.eventStore.read(ctx.sessionId), nextStageId)) {
|
||||
val cycleKey = "${ctx.currentStageId.value}->${nextStageId.value}"
|
||||
val maxIterations = ctx.graph.stages[nextStageId]?.maxRetries ?: DEFAULT_MAX_REFINEMENT
|
||||
val maxIterations = ctx.graph.stages[nextStageId]?.maxRetries ?: tuning.defaultMaxRefinement
|
||||
val iteration = (orchestrationRepository.getState(ctx.sessionId).refinementIterations[cycleKey] ?: 0) + 1
|
||||
emit(ctx.sessionId, RefinementIterationEvent(ctx.sessionId, cycleKey, iteration, maxIterations))
|
||||
if (iteration > maxIterations) {
|
||||
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package com.correx.core.kernel.orchestration
|
||||
|
||||
/**
|
||||
* Operator-tunable orchestration loop/threshold/budget knobs, loaded once at server startup from
|
||||
* the `[orchestration]` config section and threaded into the orchestrator. Defaults equal the
|
||||
* former hardcoded constants, so an absent config section reproduces prior behavior exactly.
|
||||
*
|
||||
* These are the knobs an operator reaches for when a *local* model misbehaves (thrashes a read
|
||||
* loop, re-asks the same clarification, gets trapped by a stubborn reviewer). Pure output-shaping
|
||||
* truncation caps (summary/evidence length limits) are intentionally left as constants — they don't
|
||||
* change orchestration behavior, only display width.
|
||||
*
|
||||
* ponytail: startup-load, not hot-reload — orchestrator holds this by value. If per-session
|
||||
* live-tuning is ever wanted, pass a `() -> OrchestrationTuning` supplier like the journal-compaction
|
||||
* threshold does.
|
||||
*/
|
||||
data class OrchestrationTuning(
|
||||
/** Max inference+tool rounds in a single stage's ReAct loop before it's forced to conclude. */
|
||||
val maxToolRounds: Int = 30,
|
||||
/** Consecutive read-only rounds after which the model is nudged to write. */
|
||||
val readLoopNudgeThreshold: Int = 3,
|
||||
/** Consecutive rejected-tool rounds after which the model is nudged off the blocked path. */
|
||||
val rejectionLoopNudgeThreshold: Int = 3,
|
||||
/** Max validation issues surfaced back to the model as feedback. */
|
||||
val maxFeedbackIssues: Int = 3,
|
||||
/** Top-K repo-map hits injected into a stage's context. */
|
||||
val repoMapInjectTopK: Int = 30,
|
||||
/** Files listed per directory in the what-exists repo map. */
|
||||
val repoMapFilesPerDir: Int = 8,
|
||||
/** Max docs listed in the always-on "docs available" catalog. */
|
||||
val docsCatalogMax: Int = 20,
|
||||
/** Max times a single stage may re-ask clarification before it must proceed. */
|
||||
val maxClarificationRounds: Int = 3,
|
||||
/** Minimum reviewer confidence for a correctness finding to block a stage. */
|
||||
val reviewBlockMinConfidence: Double = 0.7,
|
||||
/** Pathological backstop: max review-driven retries before the stage is let through. */
|
||||
val reviewBlockRetryCap: Int = 20,
|
||||
/** Max review→refine cycles for a stage (freestyle default refinement budget). */
|
||||
val defaultMaxRefinement: Int = 3,
|
||||
/** Budget for routing a failed write-less stage to a recovery stage. */
|
||||
val recoveryRouteBudget: Int = 2,
|
||||
/** Budget for tier-2 intent-holder arbiter re-routing. */
|
||||
val intentRouteBudget: Int = 2,
|
||||
)
|
||||
+17
-22
@@ -181,14 +181,13 @@ import kotlin.coroutines.cancellation.CancellationException
|
||||
// re-investigating (list_dir/read/rebuild) before it reaches the write, and got bounced out before
|
||||
// acting on a correct diagnosis. The real runaway guards are the read-loop and rejection-loop
|
||||
// nudges (both fire after 3 unproductive rounds), so this ceiling only needs to be generous enough
|
||||
// that productive stages aren't cut off mid-work.
|
||||
private const val MAX_TOOL_ROUNDS = 30
|
||||
// that productive stages aren't cut off mid-work. Value now lives in OrchestrationTuning.maxToolRounds.
|
||||
|
||||
// Consecutive read-only tool rounds (no file_write/file_edit) that still owe a file_written
|
||||
// artifact before we force the write nudge. A model that keeps calling read tools every round
|
||||
// trips neither the prose nudge nor the stage_complete nudge, so without this it silently burns
|
||||
// every round reading and never writes (2026-07-05: define_types read-looped 40 turns → failed).
|
||||
private const val READ_LOOP_NUDGE_THRESHOLD = 3
|
||||
// Value now lives in OrchestrationTuning.readLoopNudgeThreshold.
|
||||
|
||||
// Consecutive rounds in which EVERY tool call was rejected/denied (plane-2 BLOCKED or a stage/policy
|
||||
// ERROR) with nothing succeeding. Distinct from the read-loop breaker, which only fires for stages
|
||||
@@ -196,22 +195,19 @@ private const val READ_LOOP_NUDGE_THRESHOLD = 3
|
||||
// rejected path (e.g. list/read of a not-yet-created dir) would otherwise thrash to MAX_TOOL_ROUNDS
|
||||
// with no progress. After this many all-rejected rounds we force the model to stop retrying and
|
||||
// produce its output; a single successful tool call resets the counter.
|
||||
private const val REJECTION_LOOP_NUDGE_THRESHOLD = 3
|
||||
// Value now lives in OrchestrationTuning.rejectionLoopNudgeThreshold.
|
||||
private val WRITE_TOOL_NAMES = setOf("file_write", "file_edit")
|
||||
private const val MAX_FEEDBACK_ISSUES = 3
|
||||
private const val STAGE_COMPLETE_TOOL = "stage_complete"
|
||||
private const val EMIT_ARTIFACT_TOOL = "emit_artifact"
|
||||
private const val SCOPE_PROPOSAL_TOOL = "propose_scope"
|
||||
private const val READ_BEFORE_WRITE_CODE = "READ_BEFORE_WRITE"
|
||||
private const val REFERENCE_EXISTS_CODE = "REFERENCE_EXISTS"
|
||||
private const val OUTPUT_SUMMARY_LIMIT = 500
|
||||
private const val REPO_MAP_INJECT_TOP_K = 30
|
||||
private const val REPO_MAP_FILES_PER_DIR = 8
|
||||
// Read-on-demand doc catalog: the top-N docs (by repo-map recency score) surfaced as
|
||||
// `path — descriptor` so a stage learns which docs exist and can file_read one when relevant,
|
||||
// instead of docs being force-fed (or excluded outright). One line each, hard-capped, so it can
|
||||
// stay always-on without re-poisoning context (2026-07-07 doc-injection rework).
|
||||
private const val DOCS_CATALOG_MAX = 20
|
||||
// Value now lives in OrchestrationTuning.docsCatalogMax.
|
||||
|
||||
// A stage prompt must explicitly ask about documentation for .md/docs paths to enter the repo
|
||||
// layout listing; otherwise docs only reach context through a real retrieval hit.
|
||||
@@ -241,8 +237,7 @@ private val REQUIRED_SOURCE_TYPES = setOf(
|
||||
private const val HTTP_TIMEOUT = 408
|
||||
private const val HTTP_TOO_MANY_REQUESTS = 429
|
||||
|
||||
// Cap on clarification rounds per stage, so a stage that keeps re-asking eventually proceeds.
|
||||
private const val MAX_CLARIFICATION_ROUNDS = 3
|
||||
// Cap on clarification rounds per stage — OrchestrationTuning.maxClarificationRounds.
|
||||
|
||||
// Static-analysis output caps: the tail retained in the recorded event vs. the (larger) slice fed
|
||||
// back to the model so it can fix the failure. Tails, because the error summary sits at the end.
|
||||
@@ -255,8 +250,7 @@ private const val CONTRACT_EVIDENCE_CAP = 400
|
||||
// every attempt (so the budget never charges, i.e. the run looks like perpetual "progress"): after
|
||||
// this many blocks, findings surface without blocking so a stuck reviewer can never trap a stage
|
||||
// forever. Set well above any legitimate budget+salvage sequence. Objective text is capped too.
|
||||
private const val REVIEW_BLOCK_MIN_CONFIDENCE = 0.7
|
||||
private const val REVIEW_BLOCK_RETRY_CAP = 20
|
||||
// REVIEW_BLOCK_MIN_CONFIDENCE / REVIEW_BLOCK_RETRY_CAP now in OrchestrationTuning.
|
||||
private const val REVIEW_OBJECTIVE_CAP = 4_000
|
||||
private const val STATIC_ANALYSIS_SUMMARY_CAP = 2_000
|
||||
private const val STATIC_ANALYSIS_FEEDBACK_CAP = 6_000
|
||||
@@ -287,6 +281,7 @@ abstract class SessionOrchestrator(
|
||||
private val repoKnowledgeRetriever: RepoKnowledgeRetriever? = null,
|
||||
private val readyTaskCounter: ReadyTaskCounter? = null,
|
||||
private val taskClaimCoordinator: TaskClaimCoordinator? = null,
|
||||
protected val tuning: OrchestrationTuning = OrchestrationTuning(),
|
||||
) {
|
||||
private val log = LoggerFactory.getLogger(this::class.java)
|
||||
private val eventStore: EventStore = repositories.eventStore
|
||||
@@ -690,7 +685,7 @@ abstract class SessionOrchestrator(
|
||||
|
||||
while (
|
||||
inferenceResult is InferenceResult.Success &&
|
||||
toolRounds < MAX_TOOL_ROUNDS &&
|
||||
toolRounds < tuning.maxToolRounds &&
|
||||
(inferenceResult.response.finishReason is FinishReason.ToolCall || owesFileWrite())
|
||||
) {
|
||||
// Content turn (no tool call) but the stage still owes a file_written artifact: the
|
||||
@@ -769,7 +764,7 @@ abstract class SessionOrchestrator(
|
||||
consecutiveReadOnlyRounds = 0
|
||||
} else {
|
||||
consecutiveReadOnlyRounds++
|
||||
if (consecutiveReadOnlyRounds >= READ_LOOP_NUDGE_THRESHOLD) {
|
||||
if (consecutiveReadOnlyRounds >= tuning.readLoopNudgeThreshold) {
|
||||
consecutiveReadOnlyRounds = 0
|
||||
inferenceResult = pushBack(readLoopNudge, forceWriteOnly = true)
|
||||
continue
|
||||
@@ -789,7 +784,7 @@ abstract class SessionOrchestrator(
|
||||
toolResults.all { it.content.startsWith("BLOCKED:") || it.content.startsWith("ERROR:") }
|
||||
if (allRejected) {
|
||||
consecutiveRejectedRounds++
|
||||
if (consecutiveRejectedRounds >= REJECTION_LOOP_NUDGE_THRESHOLD) {
|
||||
if (consecutiveRejectedRounds >= tuning.rejectionLoopNudgeThreshold) {
|
||||
consecutiveRejectedRounds = 0
|
||||
inferenceResult = pushBack(
|
||||
"STOP. Your last tool calls were all rejected and retrying the same paths will " +
|
||||
@@ -1774,7 +1769,7 @@ abstract class SessionOrchestrator(
|
||||
|
||||
val priorRounds = eventStore.read(sessionId)
|
||||
.count { (it.payload as? ClarificationRequestedEvent)?.stageId == stageId }
|
||||
if (priorRounds >= MAX_CLARIFICATION_ROUNDS) return false
|
||||
if (priorRounds >= tuning.maxClarificationRounds) return false
|
||||
|
||||
val requestId = ClarificationRequestId(UUID.randomUUID().toString())
|
||||
val deferred = CompletableDeferred<List<ClarificationAnswer>>()
|
||||
@@ -1814,7 +1809,7 @@ abstract class SessionOrchestrator(
|
||||
appendLine("## Repo layout (what exists — use file_read for content)")
|
||||
byDir.entries.sortedBy { it.key }.forEach { (dir, files) ->
|
||||
val names = files.map { it.substringAfterLast('/') }
|
||||
val shown = names.take(REPO_MAP_FILES_PER_DIR)
|
||||
val shown = names.take(tuning.repoMapFilesPerDir)
|
||||
val more = names.size - shown.size
|
||||
val suffix = if (more > 0) ", …(+$more more)" else ""
|
||||
appendLine("- $dir/ (${names.size}): ${shown.joinToString(", ")}$suffix")
|
||||
@@ -1915,7 +1910,7 @@ abstract class SessionOrchestrator(
|
||||
|
||||
val retriever = repoKnowledgeRetriever
|
||||
?: return buildRepoMapEntries(sessionId, stagePrompt) + buildDocsCatalogEntry(sessionId)
|
||||
val hits = runCatching { retriever.retrieve(sessionId, stagePrompt, REPO_MAP_INJECT_TOP_K) }
|
||||
val hits = runCatching { retriever.retrieve(sessionId, stagePrompt, tuning.repoMapInjectTopK) }
|
||||
.getOrElse { e ->
|
||||
if (e is CancellationException) throw e
|
||||
log.warn("repo-knowledge retrieval failed for stage {}: {}", stageId.value, e.message)
|
||||
@@ -1942,7 +1937,7 @@ abstract class SessionOrchestrator(
|
||||
val docs = map.entries
|
||||
.filter { isDocPath(it.path) }
|
||||
.sortedByDescending { it.score }
|
||||
.take(DOCS_CATALOG_MAX)
|
||||
.take(tuning.docsCatalogMax)
|
||||
if (docs.isEmpty()) return emptyList()
|
||||
val content = buildString {
|
||||
appendLine("## Docs available (file_read to open — do not assume contents)")
|
||||
@@ -2787,11 +2782,11 @@ abstract class SessionOrchestrator(
|
||||
.mapNotNull { it.payload as? ReviewFindingsRaisedEvent }
|
||||
.count { it.stageId == stageId && it.blocked }
|
||||
val blockingFinding = outcome.findings.firstOrNull {
|
||||
it.correctness && it.confidence >= REVIEW_BLOCK_MIN_CONFIDENCE
|
||||
it.correctness && it.confidence >= tuning.reviewBlockMinConfidence
|
||||
}
|
||||
val shouldBlock = outcome.verdict == ReviewVerdict.FAIL &&
|
||||
blockingFinding != null &&
|
||||
priorBlocks < REVIEW_BLOCK_RETRY_CAP
|
||||
priorBlocks < tuning.reviewBlockRetryCap
|
||||
|
||||
emit(sessionId, ReviewFindingsRaisedEvent(sessionId, stageId, outcome.verdict, outcome.findings, blocked = shouldBlock))
|
||||
|
||||
@@ -2884,7 +2879,7 @@ abstract class SessionOrchestrator(
|
||||
return if (errors.isEmpty()) {
|
||||
"validation failed"
|
||||
} else {
|
||||
"validation failed: " + errors.take(MAX_FEEDBACK_ISSUES).joinToString("; ")
|
||||
"validation failed: " + errors.take(tuning.maxFeedbackIssues).joinToString("; ")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user