/**
 * Token-threshold heartbeat — opensquid's in-ecosystem replacement for
 * the auto-classifier subprocess.
 *
 * The agent (Claude Code itself) is the LLM in the loop and is already
 * authenticated. There is no reason to spawn a second LLM subprocess
 * to classify what the user said — the agent can do that work inline
 * per the CLAUDE.md classify-and-act block.
 *
 * What the agent IS bad at: noticing when its in-context understanding
 * has drifted enough that it should re-anchor via `recall`. That's a
 * timing problem opensquid can solve cleanly: count tokens in the
 * transcript, compare to a checkpoint, and if the delta crosses a
 * threshold, drop a one-line nudge into the next turn's UserPromptSubmit
 * stdout so the agent sees it at the top of its system context.
 *
 * Flow:
 *   Stop hook (every turn) → `checkAndMaybeArm(sessionId, transcriptPath)`
 *     - Estimates total transcript tokens (char count / 4)
 *     - Loads last checkpoint from `<data-root>/sessions/<sid>/heartbeat-checkpoint.json`
 *     - If (current - last) >= threshold, writes a marker at
 *       `<data-root>/sessions/<sid>/heartbeat-pending.txt` AND updates
 *       the checkpoint to the current count
 *   UserPromptSubmit hook (next turn) → `consumePendingHeartbeat(sessionId)`
 *     - Reads marker if present, returns its contents (for stdout injection)
 *     - Deletes the marker so the nudge fires exactly once per crossing
 *
 * Threshold: `OPENSQUID_HEARTBEAT_TOKENS` env, default 20000.
 *   Calibrated against the existing CLAUDE.md "drifts after ~10 unrelated
 *   turns" observation (~2000 tokens per turn typical).
 *
 * Zero subprocess. Zero external LLM. Zero new dependency. The agent
 * (which already exists in the loop) does the actual recall + classify
 * work inline after seeing the nudge.
 */
/** Default threshold — see file docstring for rationale. */
export declare const DEFAULT_HEARTBEAT_TOKENS = 20000;
/**
 * Resolve the configured heartbeat threshold. Env var `OPENSQUID_HEARTBEAT_TOKENS`
 * overrides; values <= 0 or non-numeric fall back to the default. Returning a
 * sentinel `Infinity` would let callers disable cheaply, but we keep the
 * contract simple: any positive integer is honored.
 */
export declare function heartbeatThresholdTokens(): number;
/**
 * Rough token estimate from raw text — char-count / 4. Cheap, dependency-
 * free, and accurate enough for "did the conversation grow by 20K tokens
 * since last checkpoint" decisions. Real tokenizers (tiktoken,
 * @anthropic-ai/tokenizer) would be more accurate but add a runtime dep
 * for marginal gain on a threshold comparison.
 */
export declare function estimateTokens(text: string): number;
export declare function estimateTranscriptTokens(transcriptPath: string): Promise<number>;
interface Checkpoint {
    last_token_count: number;
    last_checkpoint_at: string;
}
export declare function readCheckpoint(sessionId: string, options?: {
    dataRoot?: string;
}): Promise<Checkpoint | null>;
export declare function writeCheckpoint(sessionId: string, checkpoint: Checkpoint, options?: {
    dataRoot?: string;
}): Promise<void>;
/**
 * Build the one-line nudge that gets injected into the agent's next-
 * turn system context. Kept short — the agent's existing system prompt
 * (CLAUDE.md classify-and-act + the user-is-god promoted lesson)
 * already tells it what to do with classification work.
 */
export declare function formatHeartbeatNudge(delta: number, threshold: number): string;
/**
 * Stop-hook entrypoint: check whether the transcript has grown past
 * the threshold since the last checkpoint. If so, arm a pending
 * heartbeat marker for UserPromptSubmit to surface on the next turn,
 * and bump the checkpoint to the current count.
 *
 * Returns the nudge text written (or null if no arming happened) for
 * test introspection and Stop-hook stderr surfacing.
 */
export declare function checkAndMaybeArm(sessionId: string, transcriptPath: string, options?: {
    dataRoot?: string;
    thresholdTokens?: number;
}): Promise<string | null>;
/**
 * UserPromptSubmit-hook entrypoint: consume any armed heartbeat marker
 * and return its text. Caller injects via stdout. Marker is removed so
 * the nudge fires exactly once per threshold crossing.
 */
export declare function consumePendingHeartbeat(sessionId: string, options?: {
    dataRoot?: string;
}): Promise<string | null>;
/** Test/SessionEnd helper — paths the cleanup phase removes. */
export declare function heartbeatSessionFiles(sessionId: string, dataRoot?: string): string[];
/** Set the flag — called after the heartbeat nudge is surfaced. */
export declare function markRecallRequired(sessionId: string, options?: {
    dataRoot?: string;
}): Promise<void>;
/** Check whether the flag is set for this session. */
export declare function isRecallRequired(sessionId: string, options?: {
    dataRoot?: string;
}): Promise<boolean>;
/** Clear the flag — called after the agent calls recall. */
export declare function clearRecallRequired(sessionId: string, options?: {
    dataRoot?: string;
}): Promise<void>;
export {};
//# sourceMappingURL=heartbeat.d.ts.map