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,
    /** Review→rework cycles before deterministic escalation to recovery. */
    val reviewLoopMaxCycles: Int = 3,
    /** 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,
    /**
     * Cumulative same-signature tool failures within a stage before it's broken out of the loop and
     * routed to recovery (Vikunja #78). Unlike the consecutive read/rejection nudges, this survives
     * interleaved successes — it counts a normalized failure signature across the whole stage.
     */
    val stageFailureLoopLimit: Int = 6,
    /** Minimum confidence for a post-failure diagnostic proposal (#294) to be routed into recovery. */
    val diagnosisMinConfidence: Double = 0.5,
    /**
     * After this many consecutive WRITE_SCOPE/PATH_OUTSIDE_MANIFEST rejections of the SAME path in
     * a session, stop hard-rejecting and escalate to user approval instead (#301) — small models
     * frequently fail to comply with the remediation and thrash rather than widen scope themselves.
     * 0 disables escalation (falls back to the hard-block-forever behavior). A headless session
     * (no approver connected) auto-rejects the escalated prompt rather than hanging.
     */
    val escalateScopeAfterN: Int = 3,
    /**
     * Inside the recovery stage (Vikunja #309): how many times the SAME file may be rewritten
     * without its diagnostic ever clearing before the same-fingerprint loop-breaker escalates and
     * fails the run, instead of continuing an unbounded repair loop. Recovery is a single
     * continuous ReAct loop (see maxToolRounds doc), so the cumulative tool-failure breaker never
     * fires when every file_edit call itself succeeds — only the downstream diagnostic keeps
     * failing — hence this separate, path-keyed guard.
     */
    val recoveryFileRewriteLimit: Int = 3,
)
