/**
 * Internal DTOs for `core/reward`.
 *
 * The reward pipeline maps one episode → one R_human scalar →
 * reflection-weighted backprop → per-trace V_t → priority update.
 *
 * We keep things internal to the module; public exports are chosen
 * deliberately in `index.ts`.
 */
import type { EpisodeId, EpochMs, FeedbackId, FeedbackRow, SessionId, TraceId, TraceRow } from "../types.js";
export interface RewardConfig {
    /** V7 §0.6 eq. 4/5: discount factor γ. */
    gamma: number;
    /** V7 §2.4.5 eq. 3: softmax τ (used downstream by L2 induction; we just expose it here). */
    tauSoftmax: number;
    /** V7 §3.3: priority decay half-life in days. */
    decayHalfLifeDays: number;
    /** When true, call the LLM to score feedback → R_human. */
    llmScoring: boolean;
    /** Magnitude threshold (|R_human|) that triggers backprop on implicit signals. */
    implicitThreshold: number;
    /** Seconds to wait for explicit feedback. 0 disables the timer. */
    feedbackWindowSec: number;
    /** Max chars in the task summary handed to the LLM. */
    summaryMaxChars: number;
    /** Concurrency for R_human LLM calls. */
    llmConcurrency: number;
    /**
     * Minimum number of user↔assistant *exchanges* (rounds) before an
     * episode is eligible for scoring. Episodes shorter than this are
     * closed as abandoned with reason "too few conversation turns" —
     * mirroring the legacy `memos-local-openclaw` shouldSkipSummary rule
     * (chunks < 4 OR min(user, assistant) < N). Default 1
     * (relaxed in 2026Q2 to admit CLI single-shot patterns; was 2).
     */
    minExchangesForCompletion: number;
    /**
     * Minimum combined characters across all user+assistant turns before
     * scoring. Trivial banter ("hi", "ok") skips summarization even if
     * the exchange count is sufficient. Default 80 — enough for a short
     * CJK or English prompt + reply, cheap enough to run offline.
     */
    minContentCharsForCompletion: number;
    /**
     * Fraction of turns that are tool calls above which an episode is
     * flagged as "tool-heavy" (combined with low assistant text → skip).
     * Default 0.7.
     */
    toolHeavyRatio: number;
    /**
     * Minimum total assistant content chars needed to keep an episode
     * that the tool-heavy heuristic would otherwise skip. Default 80.
     */
    minAssistantCharsForToolHeavy: number;
}
/**
 * A single user-originated feedback event. `explicit` = user literally said
 * something ("good job" / "no, try X"); `implicit` = inferred from the
 * session flow (follow-up within ε ms is positive; edited artifact is
 * negative; abandonment is −1).
 */
export interface UserFeedback {
    id: FeedbackId;
    episodeId: EpisodeId;
    sessionId: SessionId;
    traceId?: TraceId | null;
    ts: EpochMs;
    /** Channel as stored in the `feedback` table. */
    channel: FeedbackRow["channel"];
    polarity: FeedbackRow["polarity"];
    magnitude: number;
    text: string | null;
    rationale: string | null;
}
export interface HumanScore {
    /** R_human ∈ [-1, 1]. */
    rHuman: number;
    /** Per-axis sub-scores (see V7 §0.6 table). */
    axes: {
        goalAchievement: number;
        processQuality: number;
        userSatisfaction: number;
    };
    /** One-sentence justification from the rubric LLM. */
    reason: string | null;
    /** "llm" when we actually called it, "heuristic" when we fell back. */
    source: "llm" | "heuristic" | "explicit";
    /** Which provider/model served the scoring call, if any. */
    model: string | null;
}
/** Inputs passed to `scoreHuman`. */
export interface HumanScoreInput {
    episodeSummary: TaskSummary;
    feedback: readonly UserFeedback[];
}
export interface TaskSummary {
    episodeId: EpisodeId;
    sessionId: SessionId;
    /** Optional host/evaluator context included in the packed summary. */
    hostContext?: string;
    userQuery: string;
    agentActions: string;
    outcome: string;
    /** Full packed text (≤ summaryMaxChars). */
    text: string;
    /** True when the summary had to drop content to fit. */
    truncated: boolean;
}
export interface BackpropInput {
    /** Traces belonging to the episode, in chronological order (small → large ts). */
    traces: readonly TraceRow[];
    /** R_human for the episode. */
    rHuman: number;
    /** Discount factor γ ∈ [0, 1]. */
    gamma: number;
    /** Decay half-life in days (for priority). */
    decayHalfLifeDays: number;
    /** Anchor time for the decay calculation (ms). Defaults to `Date.now()`. */
    now?: EpochMs;
}
export interface BackpropUpdate {
    traceId: TraceId;
    /** V_t ∈ [-1, 1]. */
    value: number;
    /** α carried over from capture (not recomputed). */
    alpha: number;
    /** priority ∝ max(V, 0) · decay(Δt). */
    priority: number;
}
export interface BackpropResult {
    updates: BackpropUpdate[];
    /** Mean |V| over episode — tracked for debugging. */
    meanAbsValue: number;
    /** Max priority (debug). */
    maxPriority: number;
    /** γ / half-life / anchor recorded for audit. */
    echoParams: {
        gamma: number;
        decayHalfLifeDays: number;
        now: EpochMs;
    };
}
export interface RewardResult {
    episodeId: EpisodeId;
    sessionId: SessionId;
    /** R_human ∈ [-1, 1]. */
    rHuman: number;
    /** The HumanScore record that produced R_human. */
    humanScore: HumanScore;
    /** Count of feedback rows that contributed to this score. */
    feedbackCount: number;
    /** Backprop summary. */
    backprop: BackpropResult;
    /** Traces that were updated (ids only). */
    traceIds: TraceId[];
    /** Monotonic timings for the run. */
    timings: {
        summary: number;
        score: number;
        backprop: number;
        persist: number;
        total: number;
    };
    /** Non-fatal hiccups that don't stop the run. */
    warnings: Array<{
        stage: string;
        message: string;
        detail?: Record<string, unknown>;
    }>;
    startedAt: EpochMs;
    completedAt: EpochMs;
}
export interface RewardInput {
    episodeId: EpisodeId;
    /**
     * Feedback items that arrived for this episode. MAY be empty — the
     * pipeline will then fall back to implicit-signals scoring (default
     * neutral if nothing is available).
     */
    feedback: readonly UserFeedback[];
    /**
     * "trigger" is metadata only; it lets downstream subscribers tell
     * whether the run was the automatic window-expired fallback vs. an
     * explicit user reply.
     */
    trigger: "explicit_feedback" | "implicit_fallback" | "manual";
}
export type RewardEvent = {
    kind: "reward.scheduled";
    episodeId: EpisodeId;
    sessionId: SessionId;
} | {
    kind: "reward.scored";
    episodeId: EpisodeId;
    sessionId: SessionId;
    rHuman: number;
    source: HumanScore["source"];
} | {
    kind: "reward.updated";
    result: RewardResult;
} | {
    kind: "reward.failed";
    episodeId: EpisodeId;
    sessionId: SessionId;
    stage: string;
    error: {
        code: string;
        message: string;
    };
};
export type RewardEventKind = RewardEvent["kind"];
export type RewardEventListener = (evt: RewardEvent) => void;
export interface RewardEventBus {
    on(kind: RewardEventKind, fn: RewardEventListener): () => void;
    onAny(fn: RewardEventListener): () => void;
    emit(evt: RewardEvent): void;
    listenerCount(kind?: RewardEventKind): number;
}
//# sourceMappingURL=types.d.ts.map