/**
 * PlacementExecutor — the single canonical placement component (Multi-Machine
 * Session Pool §L4). Per "Structure > Willpower", placement is NOT pluggable
 * per-agent code: ALL placement decisions route through this one component, and
 * its policy is structured DATA (JSON) validated against a fixed schema — never
 * ad-hoc logic. `decide()` is PURE over its inputs (deterministic, unit-testable);
 * the CALLER (router) performs the CAS + side effects from the returned decision.
 */
import type { MachineCapacity } from './types.js';
/** Per-topic placement metadata (persisted with the topic; §L4). */
export interface TopicPlacement {
    /** A specific machine the session prefers / is pinned to (nickname-resolved → machineId). */
    preferredMachine?: string;
    /** Hard capability requirements (e.g. "gpu", "local-model:llama3"). */
    requiredCapabilities?: string[];
    /** When true + preferredMachine set → a HARD pin (queue+escalate if that machine is unavailable). */
    pinned?: boolean;
    lastTransferredAt?: number;
    queueReason?: string;
}
export interface PlacementPolicy {
    weights: {
        loadAvg: number;
        activeSessionRatio: number;
        memPressure: number;
    };
    thresholds: {
        rebalanceThresholdPercent: number;
        placementHysteresisDelta: number;
    };
    capabilityWhitelist: string[];
    ordering: Array<'hard-constraint' | 'pin' | 'sticky' | 'least-loaded'>;
}
export declare const DEFAULT_PLACEMENT_POLICY: PlacementPolicy;
export interface PlacementRequest {
    sessionKey: string;
    topicMetadata: TopicPlacement;
    machineRegistry: MachineCapacity[];
    currentOwner?: string;
    reason: 'new' | 'failover' | 'rebalance' | 'pin';
    /** The channel scope (platform + telegram chatId / slack workspaceId + channelId) this placement
     *  is for — threaded from the caller so placement can avoid a machine whose adapter can't reach it
     *  (spec: placement-platform-workspace-aware). Absent (legacy caller) → every machine resolves
     *  `unknown` → the platform/workspace filter no-ops (fail-open). The FAILOVER caller MUST pass it
     *  (the live-test bug was a failover placement). */
    channel?: import('./machineServesChannel.js').ChannelScope & {
        channelId?: string;
    };
}
export interface PlacementDecision {
    chosenMachine: string | null;
    score: number;
    reason: string;
    outcome: 'placed' | 'queued' | 'placement-blocked';
    escalationReason?: string;
}
/**
 * Validate a PlacementPolicy against the fixed schema (§L4). Throws on a malformed
 * policy — unknown weight key, non-numeric threshold/weight, or a capability token
 * outside the whitelist vocabulary — so a router refuses to act with a bad policy
 * (never silently defaulted). Called at startup.
 */
export declare function validatePlacementPolicy(policy: unknown): asserts policy is PlacementPolicy;
/** Validate per-topic placement metadata on read (§L4) — never infer/sanitize; block+escalate on a violation. */
export declare function validateTopicPlacement(tp: unknown, capabilityWhitelist: string[]): {
    ok: true;
} | {
    ok: false;
    reason: string;
};
export declare class PlacementExecutor {
    private readonly policy;
    constructor(policy?: PlacementPolicy);
    /** Score a candidate (lower = better): weighted load + session-ratio + mem pressure. */
    private score;
    /** Pure placement decision (§L4). Deterministic over its inputs. */
    decide(req: PlacementRequest): PlacementDecision;
}
//# sourceMappingURL=PlacementExecutor.d.ts.map