import type { QuotaBoundaryStatus } from './quota-boundary.js';
import type { AutoHandoffSkip } from './events.js';
/**
 * Auto PM (#685): spend leftover subscription quota on product management instead of
 * letting it expire. While the account is still under its quota boundary (#879) and nobody
 * is at the keyboard, the daemon runs the cycle by itself: it works the agent queue down entry by entry (#855),
 * and once that is empty it refills it — triaging tickets, then spiking and planning
 * the ones that have neither yet.
 *
 * The whole feature is one policy question ("is now a good time to spend tokens on our
 * own roadmap?"), so that question lives here as a pure function and the daemon only
 * supplies the readings. #298 is the parent idea (background jobs / "max out the usage"),
 * and #879 defines the boundary this reads.
 */
/** How often the daemon re-asks {@link autoPmDecision}. */
export declare const DEFAULT_AUTO_PM_INTERVAL_MS: number;
/**
 * How long a project is left alone after an auto agent is started for it. A spawned agent
 * takes a moment to appear in the daemon's live-run map, and without this the next tick
 * would see "nothing running, queue still empty" and start a second one.
 */
export declare const DEFAULT_AUTO_PM_COOLDOWN_MS: number;
/** What the policy was told about one project at one moment. */
export interface AutoPmInputs {
    /** The `autoPm` preference. Off = the feature does nothing at all. */
    enabled: boolean;
    /**
     * Whether the project's agent queue (`TODO_AGENTS.md`) has no open entry left, or `undefined`
     * when it could not be read. Unreadable is not empty and not full: it fails closed, because
     * since #855 both answers now *start* something and only "we could not tell" does not.
     */
    backlogEmpty: boolean | undefined;
    /** Live agents on this project, measured against {@link AutoPmInputs.concurrency}. */
    activeAgents: number;
    /**
     * How many agents the routine may keep going on this project at once (#1204);
     * {@link DEFAULT_AUTO_PM_CONCURRENCY} when unset. Floored at one, because zero concurrent
     * agents is what `enabled: false` already spells.
     */
    concurrency?: number;
    /** Where the account stands against its quota boundary, or `undefined` when it could not be read. */
    quota: QuotaBoundaryStatus | undefined;
    /** Milliseconds since this project was last auto-started, or `undefined` if it never was. */
    sinceLastStartMs?: number;
    /** Override {@link DEFAULT_AUTO_PM_COOLDOWN_MS}. */
    cooldownMs?: number;
}
/** Why the sweep is not starting anything. Logged, so it reads as a sentence. */
export type AutoPmRefusal = {
    start: false;
    reason: string;
};
/**
 * Which half of the cycle a start belongs to (#855). `drain` works an entry the queue already
 * holds; `pm` puts new work in it. The queue decides: standing work is spent before more is made.
 */
export type AutoPmMode = 'drain' | 'pm';
/** Whether the budget allows spending unasked at all, before asking what to spend it on. */
export type QuotaDecision = {
    start: true;
} | AutoPmRefusal;
/** Start (and at what), or the reason not to. */
export type AutoPmDecision = {
    start: true;
    mode: AutoPmMode;
} | AutoPmRefusal;
/**
 * Whether the budget allows spending unasked.
 *
 * The gate is the quota boundary (#879): the pro-rated share of the week's allowance elapsed so
 * far, rising continuously with the clock (#960 Edit), so auto PM spends up to that line and
 * stands down at it. Work the user asks for is free to cross it and borrow against the days still
 * to come; work nobody asked for is exactly what the line is there to stop.
 *
 * **It fails closed on a quota it cannot read, and that is the opposite of the per-agent guard.**
 * #519 settled that an unreadable quota must never *stop* the user's own work, so
 * `startConsumptionGuard` fails open. Quietly burning a subscription on work nobody asked for
 * is a far worse failure than skipping a tick.
 *
 * Reading the account's own week also means a restarted daemon is not blind: the figure is
 * absolute and complete, unlike the delta meter this replaced, which reported zero consumed
 * after a restart however much the account had spent (#848).
 */
export declare function quotaHeadroom(quota: QuotaBoundaryStatus | undefined): QuotaDecision;
/**
 * Whether to start a PM agent for one project right now. Every condition is a reason to
 * *not* spend the user's quota, checked cheapest first so the common "someone is working"
 * case never reaches the meter.
 */
export declare function autoPmDecision(input: AutoPmInputs): AutoPmDecision;
/**
 * One thing auto PM knows how to do while the machine is idle (#773).
 *
 * The jobs form a cycle, and the order matters: triage turns tickets into queued work, [Plan
 * tickets] turns the rest into plans. Once a job queues something the sweep switches to draining it
 * (#855), and the rotation resumes where it left off once the queue is empty again.
 */
export interface AutoPmJob {
    /** Stable id: the rotation and the opt-out list (#1209) key on it. */
    name: string;
    /** The prompt to run, verbatim. */
    prompt: string;
    /**
     * A line saying what the job does, wherever its {@link label} does not already: under the label
     * in the routines list, and as the log line's wording. Only the maintenance sweep carries one --
     * "Maintenance" names its preset rather than the work -- while the other routines' labels read
     * as what they do, so their rows stay one line and their log lines say the label itself rather
     * than the same thing twice. Data on the job rather than a name matched in the dashboard, so a
     * rename cannot quietly move the line around.
     */
    describe?: string;
    /**
     * The user-facing name, for a surface that lists the routines (#1159). Read off the preset the
     * job fires rather than written again here, so a relabelled preset relabels its routine.
     */
    label?: string;
    /**
     * The preset's own one-line "what this does" (#1506), for a surface that has to say what a click
     * is about to spend an agent on before it spends it. Read off the preset like
     * {@link AutoPmJob.label}, so the sentence the launcher shows for a preset and the sentence the
     * routines list shows for its routine are the same sentence. Absent for a preset without one.
     */
    tooltip?: string | undefined;
    /**
     * This job works an entry already on the queue, rather than putting entries on it (#1117).
     *
     * Only the draining job has a specific piece of work it is about to pick up, so only it can be
     * told which ticket that is. Declared here rather than matched on {@link AutoPmJob.name} at the
     * call site, so the job says what it does and a rename cannot quietly unhook it.
     */
    drains?: boolean;
    /**
     * The one queue entry a {@link AutoPmJob.drains} job is pinned to (#1204). Set only on the
     * per-start variants {@link pinnedDrainJob} builds: the catalog's own drain job carries none,
     * since which entry is next is known only at the moment the sweep starts one.
     */
    entry?: string;
    /**
     * The branch this job's prompt pins via its constant session name, when it does (#1293). The
     * triage prompts abort when `the-framework/<SESSION_NAME>` already exists, so a leftover branch
     * whose PR was closed or merged jams the routine forever. Declared as data on the job, like
     * {@link AutoPmJob.drains}, so the sweep can release the stale name before firing without
     * matching on {@link AutoPmJob.name} at the call site.
     */
    pinnedBranch?: string;
    /**
     * Merge this job's PR once its agent opens it (#1216). Set on the drain job: what it implements
     * has already been triaged as consensual, quick-win work a human could have vetoed on the
     * queue, so its PR is the one kind whose review happened before the agent. Declared as data on
     * the job for the same no-name-matching reason as {@link AutoPmJob.drains}.
     */
    autoMerge?: boolean;
    /**
     * This rotation job may fan out to several agents pinned one ticket each (#1327). Only
     * [Plan tickets] declares it: unlike the other rotation jobs it writes per-ticket sibling files
     * rather than rewriting the shared queue document, so concurrent copies do disjoint work and
     * land disjoint edits — the exact property that already lets draining fan out. Declared as data
     * on the job for the same no-name-matching reason as {@link AutoPmJob.drains}.
     */
    fansOut?: boolean;
    /**
     * The one ticket a fanned-out job is pinned to (#1327), as its filename inside `tickets/`. Set
     * only on the per-start variants {@link pinnedPlanJob} builds, like {@link AutoPmJob.entry} is
     * for drains: which tickets are open is known only at the moment the sweep locks them.
     */
    ticket?: string;
    /**
     * The `.lock.md` claim the sweep minted for this start (#1420/#1583), on both pinned variants.
     * What lets the sweep free the claim itself when the agent settles with nothing to hand off:
     * the agent's PR is what normally deletes the lock, and an agent that never made a commit is
     * never opening one.
     */
    claim?: PlanAssignment;
}
/** One fanned-out agent's claim (#1327): the ticket it is pinned to, and the id its lock names. */
export interface PlanAssignment {
    /** The ticket's filename inside `tickets/`. */
    ticket: string;
    /** What the `.lock.md`'s `CLAIMED: <AGENT_ID>` line carries (#1420), so an agent can tell its
     *  own claim from another's. Generated by the sweep, since no agent id exists before the agent
     *  starts. */
    agentId: string;
}
/**
 * A drain job pinned to one named queue entry (#1204).
 *
 * With a single agent the stock prompt ("the FIRST open entry") is exact. With several going at
 * once it is a collision: every drain forks the same checkout, so every one of them reads the same
 * first entry and implements it as many times over. Naming the entry is what makes a batch of
 * drains work on disjoint things.
 *
 * The prompt also tells the agent to stop when the entry is already checked off or gone: the
 * assignment is a snapshot, and a human may retire the entry between the sweep's read and the
 * run's own.
 *
 * When the entry links back to a ticket the sweep has claimed (#1420), the agent is also told
 * which claim is its own — the same contract {@link pinnedPlanJob} carries, because the same
 * gap exists: without the lock the claim on the *implementation* lived only in this daemon's
 * memory, so another machine's sweep could book the same ticket. Ticket, plan and lock live on
 * the data branch (#1582), so the agent retires them there once its work is published — nothing
 * else releases a lock since #1420 dropped the timer. The queue entry itself is NOT the agent's
 * to touch: the daemon checks it off at settle, once the run's ending reports the work landed.
 */
export declare function pinnedDrainJob(job: AutoPmJob, entry: string, assignment?: PlanAssignment): AutoPmJob;
/**
 * A fan-out job pinned to one locked ticket (#1327).
 *
 * The stock prompt covers every ticket that has no plan or claim yet, and with a batch going out
 * that instruction is the same collision {@link pinnedDrainJob} exists for: every agent forks the
 * same checkout and would pick the same most-important ticket. The pin is *appended* to the stock
 * prompt rather than spliced into it, so the verdict rules the preset carries keep riding along
 * verbatim and a rewritten preset (the maintainer owns its wording) cannot silently lose the pin.
 *
 * The agent is also told which claim is its own: its ticket's `.lock.md` already exists with the
 * `CLAIMED:` line the daemon pushed (#1420), and finding anything else there means the
 * assignment is stale — another agent's claim, or work that landed meanwhile — so it stops. It
 * is told to delete the lock in the same data-branch commit as the plan (#1582), because nothing
 * else releases it: #1420 removed the staleness timer, so a forgotten lock stands until a human
 * clicks it away.
 */
export declare function pinnedPlanJob(job: AutoPmJob, assignment: PlanAssignment): AutoPmJob;
/**
 * The default cycle: bring the tickets across from GitHub (#1208), triage the quick ones (#891),
 * then the significant-but-agreed ones (#892), and only then make more plans (#685). Planning is
 * the most expensive turn and the one whose output the earlier jobs consume, so it runs last.
 *
 * Importing leads because it is the only job that can add a ticket none of the others have seen
 * (#1334): a routine that triages and plans a set nothing ever refills eventually has nothing
 * left to do, and a new issue would wait for a human to press the button. It is safe to repeat --
 * the preset resumes from `tickets/meta.json`'s `lastImportedAt` and reconciles, so a firing with
 * nothing changed since the last one is a no-op rather than a re-import.
 *
 * This rotation is what #891/#892 mean by "with a cron job regularly firing this preset". No
 * separate scheduler is involved and none is needed: the rotation already fires on every idle tick
 * where the queue is dry, which is exactly when the queue wants refilling. That is the opposite of
 * the maintenance sweep (#882), which is paced by a calendar because it looks at static history and
 * would otherwise never come due — hence its own {@link AUTO_PM_MAINTENANCE_JOB} outside the cycle.
 *
 * The gated triage sibling (#698) is deliberately not here: it ends in `<AWAIT>`, so firing it with
 * nobody at the keyboard would park an agent against a human who will never answer.
 *
 * Each triage prompt pins its own session name and aborts if that branch already exists, so a
 * rotation that comes round again while the previous triage is still in flight is a no-op rather
 * than a duplicate. The rotation still advances past it, which is the wanted behaviour: the next
 * idle tick tries the next job instead of retrying a job that is already running.
 */
export declare const AUTO_PM_JOBS: readonly AutoPmJob[];
/**
 * The job for a queue that is not empty (#855): work its first entry off. Outside the rotation
 * on purpose — the rotation is about what to *make* when there is nothing to do, and this is
 * the thing to do.
 */
export declare const AUTO_PM_DRAIN_JOB: AutoPmJob;
/**
 * The periodic codebase-wide sweep (#882): fire the [Maintenance] preset (#881) so a repo that
 * adopted The Framework late gets its pre-existing history looked at.
 *
 * Outside the rotation, like {@link AUTO_PM_DRAIN_JOB} and for the same kind of reason: the
 * rotation is "what to make next" and cycles every idle tick, while this is paced by a calendar
 * and must not advance or be advanced by the cycle. It takes precedence over the rotation when
 * due, because the entries it queues are what the rotation would otherwise be inventing work
 * instead of.
 *
 * The prompt renders at module load with no session, so `tf.params.what` falls back to its
 * default of the entire codebase, which is exactly this job's scope.
 */
export declare const AUTO_PM_MAINTENANCE_JOB: AutoPmJob;
/**
 * Every routine the sweep can fire, in the order a surface should list them (#1159).
 *
 * Derived from the three constants above rather than written out again, so the list the dashboard
 * shows and the jobs the daemon actually runs cannot drift. The order is the sweep's own precedence
 * (#855/#882 read the other way round): draining comes first because it is what happens whenever
 * there is queued work, the rotation is what happens when there is not, and the calendar-paced
 * maintenance sweep is the exception outside both.
 */
export declare const AUTO_PM_ROUTINES: readonly AutoPmJob[];
/**
 * What became of one attempt to land an agent's queue (#852). The two flags are separate on purpose:
 * a finished agent that wrote no queue is `settled` without being `promoted`, and must stop being
 * retried; a still-running one is neither, and is tried again next tick.
 */
export interface PromoteOutcome {
    /** Stop tracking this agent: it is finished, whether or not it left anything behind. */
    settled: boolean;
    /** The checkout's queue actually changed. */
    promoted: boolean;
    /**
     * Why the settled run's handoff skipped, when its record says so (#1583). Rides on this outcome
     * because the promotion already read the run's record, and the sweep needs exactly one fact
     * from it: a run that ended `no-commits` will never open the PR that lifts the `.lock.md` it
     * was started under, so the sweep releases that claim itself.
     */
    handoffSkip?: AutoHandoffSkip;
    /**
     * The run finished cleanly but its epilogue has not reported yet (#1583): the `end` lands
     * before the handoff does its work, so a sweep can catch the gap between them. A claim-carrying
     * agent observed there is held pending a little longer rather than settled — settling would
     * drop the claim with the ending unread, and the release would be missed for good.
     */
    handoffPending?: boolean;
}
/** A project the sweep considers. */
export interface AutoPmProject {
    /** Registry id, as `start` and the live-agent lookup take it. */
    id: string;
    /** Absolute repo path, for reading its queue. */
    path: string;
}
/** The readings and effects {@link startAutoPm} needs, injected so the loop is testable off disk. */
export interface AutoPmDeps {
    /** The projects to consider. */
    projects(): Promise<readonly AutoPmProject[]>;
    /** The `autoPm` preference, re-read per tick so the toggle takes effect without a restart. */
    enabled(): Promise<boolean>;
    /**
     * The routines the user has switched off, by {@link AutoPmJob.name} (#1209). Re-read per tick
     * for the same reason {@link AutoPmDeps.enabled} is, and an unreadable answer means none:
     * a preference that cannot be read must not silently switch the whole rotation off.
     */
    optedOut?(): Promise<readonly string[]>;
    /**
     * The open entries of a project's agent queue, in file order. Empty = the queue has run dry, and
     * a rejection = it could not be read, which fails closed exactly as the old boolean did (#855).
     * The entries themselves rather than just emptiness, because a batch of drains is pinned one
     * entry each (#1204) and the assignment has to come from the read the decision was made on.
     */
    queue(project: AutoPmProject): Promise<readonly string[]>;
    /** How many agents are live on a project. */
    activeAgents(project: AutoPmProject): number;
    /**
     * How many agents the routine may keep going per project (#1204). Re-read per tick like
     * {@link AutoPmDeps.enabled}, so the setting takes effect without a restart. Unset or unreadable
     * falls back to {@link DEFAULT_AUTO_PM_CONCURRENCY} rather than to one: the absence of the
     * setting has never meant "less".
     */
    concurrency?(): Promise<number | undefined>;
    /**
     * Where the account stands against its boundary for the work *this project* would start, or
     * `undefined` when there is no reading.
     *
     * Asked per project rather than once per sweep (#1619): the model is a project-resolvable
     * setting, and the model's own weekly window binds alongside the account's (#879) — so two
     * projects on two models can stand at two different places against the same reading.
     */
    quota(project: AutoPmProject): Promise<QuotaBoundaryStatus | undefined>;
    /** The jobs to rotate through, in cycle order. Used only while the queue is empty. */
    jobs: readonly AutoPmJob[];
    /** The job for a queue with open entries (#855); {@link AUTO_PM_DRAIN_JOB} by default. */
    drainJob?: AutoPmJob;
    /**
     * Whether a project is due its periodic codebase sweep (#882). Injected rather than computed
     * here because the schedule lives in a file in the project checkout, and this module is pure
     * policy. Omitted entirely (or throwing) means "not due", so a daemon that cannot read the
     * schedule keeps doing the rotation rather than sweeping on every tick.
     */
    maintenanceDue?(project: AutoPmProject): Promise<boolean>;
    /** Stamp a project as swept, so the next sweep is an interval away. Paired with {@link AutoPmDeps.maintenanceDue}. */
    recordMaintenance?(project: AutoPmProject): Promise<void>;
    /** The job fired when {@link AutoPmDeps.maintenanceDue} says yes; {@link AUTO_PM_MAINTENANCE_JOB} by default. */
    maintenanceJob?: AutoPmJob;
    /** Start the PM agent. Resolves the agent's id, or undefined when the daemon refused. */
    start(project: AutoPmProject, job: AutoPmJob): Promise<string | undefined>;
    /**
     * Release a {@link AutoPmJob.pinnedBranch} its closed PR left behind (#1293), called right
     * before such a job fires. Injected because the release reads `gh` and mutates git, and this
     * module is pure policy. Omitted (or throwing) means the branch stays and the job's own abort
     * guard decides, exactly as before the seam existed.
     */
    releasePinned?(project: AutoPmProject, branch: string): Promise<unknown>;
    /**
     * Settle a finished agent's queue entry (#852/#1582): the daemon retires the entry it pinned to
     * the run once the run's ending says the work was published. Called before the sweep decides
     * anything, so an entry whose run just landed stops reading as open work to start over again.
     */
    promote(project: AutoPmProject, agent: {
        agentId: string;
        entry?: string;
    }): Promise<PromoteOutcome>;
    /**
     * The tickets open for planning (#1327): no plan or `.lock.md` claim yet (#1420) — most
     * important first, as filenames inside `tickets/`. Asked only when the tick lands on a
     * {@link AutoPmJob.fansOut} job. Unreadable means none, and no seam at all means the stock
     * single driver: the fan-out is an addition, not a precondition, and a loop wired without it
     * behaves exactly as before #1327.
     */
    planCandidates?(project: AutoPmProject): Promise<readonly string[]>;
    /**
     * Claim `assignments`' tickets before their agents start (#1327/#1420): one `.lock.md` sibling
     * per ticket reading `CLAIMED: <AGENT_ID>`, committed as one batch and pushed to the default
     * branch, so agents forked from any checkout — and cloud sessions, which is the point — find
     * the file and skip the ticket. Resolves the subset actually locked: a ticket lost to a race
     * locks fewer, and each missing lock costs one agent of the batch rather than the batch.
     * Failing (or absent) resolves nothing locked, and the sweep falls back to the stock single
     * agent — one unpinned agent is what ran before #1327 and needs no lock to be safe.
     */
    lockPlans?(project: AutoPmProject, assignments: readonly PlanAssignment[]): Promise<readonly PlanAssignment[]>;
    /**
     * Claim the tickets a drain batch is about to implement (#1420), the same way
     * {@link AutoPmDeps.lockPlans} claims them for planning — one pushed `.lock.md` per ticket —
     * but skipping only on an existing lock: a `.plan.md` is the drain's input, not a competing
     * claim. Asked only for the entries that link back to a ticket; a self-contained TODO has
     * nothing on disk to lock and keeps the queue document as its coordination point. Resolves the
     * subset actually locked — an entry whose ticket was claimed elsewhere is dropped from the
     * batch, and the next tick reconsiders it. Absent, drains run exactly as before this seam:
     * the in-memory pin still guards this daemon's own fan-out.
     */
    lockDrains?(project: AutoPmProject, assignments: readonly PlanAssignment[]): Promise<readonly PlanAssignment[]>;
    /**
     * Free a claim this loop minted whose agent settled with nothing to hand off (#1583): the
     * lock's normal release is the agent's own PR deleting it, and a run whose handoff skipped as
     * `no-commits` is never opening one — without this the queue livelocks on the dead claim until
     * a human clicks Release. Keyed off the run's recorded ending, never a timer (#1420). Only the
     * exact minted claim is freed — the callee leaves a lock naming anyone else alone.
     *
     * Resolves `true` when the claim is dealt with (freed, already gone, or someone else's), and
     * `false` when the release could not land — a transient `index.lock`, say — so the loop holds
     * the agent and tries again next sweep rather than losing the one shot. Absent (or throwing)
     * leaves the lock standing, exactly as before this seam.
     */
    releaseLock?(project: AutoPmProject, claim: PlanAssignment): Promise<boolean>;
    /** Progress line. */
    log(message: string): void;
    /** Override the tick interval. */
    intervalMs?: number;
    /** Override the per-project cooldown. */
    cooldownMs?: number;
    /** Clock, injectable for tests. */
    now?: () => number;
}
/** What the last sweep decided about one project. */
export interface AutoPmOutcome {
    /** Registry id of the project considered. */
    projectId: string;
    /** Its path, which is what the log line names and the panel shows. */
    path: string;
    /** Whether an agent was started for it. */
    started: boolean;
    /** The sentence: what was started, or the reason for standing down. */
    message: string;
}
/**
 * What auto PM has done lately (#1161).
 *
 * Every decision was already logged (#855), but the log is the daemon's stdout and the toggle
 * lives in a browser, so from the dashboard a wedged sweep and a healthy idle one looked
 * identical — the same failure #855 fixed one layer down.
 */
export interface AutoPmReport {
    /** Whether the preference was on at the last sweep. `undefined` before the first one. */
    enabled?: boolean;
    /** When the last sweep finished, epoch ms. `undefined` before the first one. */
    sweptAt?: number;
    /** When the next sweep is due, epoch ms. */
    nextSweepAt: number;
    /** One line per project the last sweep considered, in sweep order. */
    outcomes: AutoPmOutcome[];
}
/**
 * Where the dashboard reads {@link AutoPmReport} from. The daemon wires its live loop; a public
 * host (the relay) leaves it unset, and one that has not finished starting answers `undefined`.
 */
export type AutoPmReporter = () => AutoPmReport | undefined;
/** A running sweep. */
export interface AutoPmLoop {
    /**
     * Run one sweep now, rather than waiting for the next tick. Called when the preference is
     * switched on (#1161) as well as from tests: the sweep re-reads it per tick, so without this
     * the box you just ticked does nothing at all for a whole interval.
     *
     * `onDemand` marks a sweep a person explicitly asked for (#1210's trigger button). The `autoPm`
     * preference is consent to spend quota *unasked*, and a click is asking — so an on-demand sweep
     * runs with the preference off, and the master switch is the only gate it skips: every other
     * reason to stand down (live agents, cooldowns, the quota boundary, unticked routines) still holds.
     *
     * `drainOnly` narrows the sweep to working the queue (#1204): the drain row's Run now means
     * "spin agents up on the queue", so a tick that would fall through to a rotation job (the queue
     * is empty) says so instead of borrowing the click for work nobody asked for.
     */
    tick(opts?: {
        onDemand?: boolean;
        drainOnly?: boolean;
    }): Promise<void>;
    /** What the last sweep decided, for the dashboard to show (#1161). */
    report(): AutoPmReport;
    stop(): void;
}
/**
 * Start the auto-PM sweep (#685): every {@link DEFAULT_AUTO_PM_INTERVAL_MS}, ask
 * {@link autoPmDecision} for each project and start an agent for the ones that say yes.
 *
 * Ticks never overlap — a sweep reads a live-agent map that its own `start` calls mutate,
 * so a second sweep running over the first would decide against a stale picture.
 *
 * Nothing here survives the daemon: per #519 a Ctrl+C that stops everything is the feature,
 * not a gap, so this loop is deliberately not restartable from outside the process.
 */
export declare function startAutoPm(deps: AutoPmDeps): AutoPmLoop;
//# sourceMappingURL=auto-pm.d.ts.map