/** * Root-session reflection cue + explicit learning-write controller. * * Automatic reflection is a hidden cue in the root orchestrator's ordinary provider turn. This * controller never schedules an automatic provider request. The isolated completion and structured * reflection pass remain explicit compatibility/application seams for owner-invoked callers. Every * durable effect goes through the bundled memory tool, the session log (via deps), or the skills dir. */ import type { Agent } from "@caupulican/pi-agent-core/agent"; import type { SessionManager } from "@caupulican/pi-agent-core/session"; import type { AgentMessage, ThinkingLevel } from "@caupulican/pi-agent-core/types"; import type { Api, Model, SimpleStreamOptions, Usage } from "@caupulican/pi-ai/types"; import { type IsolatedCompletionOptions, type IsolatedCompletionResult } from "./agent-session-contracts.ts"; import type { LearningDecision } from "./autonomy/contracts.ts"; import { type AutonomyTelemetryEvent } from "./autonomy/telemetry-events.ts"; import { type DurableLearningClaimToken, type DurableLearningCueAttachOutcome, type DurableLearningReviewMetadata, type DurableLearningState } from "./learning/durable-learning-state.ts"; import { type LearningAuditRecord } from "./learning/learning-audit.ts"; import { type DemandSignals, type ReflectionResult } from "./learning/reflection-engine.ts"; import { type ReflectionTurnAnalysis } from "./learning/reflection-turn-analysis.ts"; import type { MemoryManager } from "./memory/memory-manager.ts"; import type { StructuredReflectionApplyResult, StructuredReflectionRollback, StructuredReflectionWrite } from "./memory/providers/file-store.ts"; import type { ModelRegistry } from "./model-registry.ts"; import type { SettingsManager } from "./settings-manager.ts"; import type { Skill } from "./skills.ts"; export interface ReflectionControllerDeps { /** Current session model (fallback for an isolated call that omits its own model). */ getModel(): Model | undefined; /** The underlying agent — its `streamFn` runs the isolated completion. */ getAgent(): Agent; /** True when the session's stream fn is the raw `streamSimple` provider (auth must be injected). */ isRawStreamSimple(): boolean; /** Model registry for API-key/header resolution on the raw-provider path. */ getModelRegistry(): ModelRegistry; /** Memory subsystem — the bundled `memory` tool applies durable writes; fresh block feeds reflection. */ getMemoryManager(): MemoryManager; /** Fresh bounded structured OKF snapshot, read at reflection time for confront-before-write. */ getFreshOkfMemoryForReflection(): string; /** Main-orchestrator-only structured memory mutation port. */ applyStructuredReflectionWrite(write: StructuredReflectionWrite, signal?: AbortSignal): Promise; /** Main-orchestrator-only inverse for an audited structured write. */ rollbackStructuredReflectionWrite(rollback: StructuredReflectionRollback, signal?: AbortSignal): Promise; /** Settings — the learning-apply policy (gate thresholds, auto-apply layers) is read here. */ getSettingsManager(): SettingsManager; /** Session log — audit snapshots and learning-audit reads go through this. */ getSessionManager(): SessionManager; /** Agent dir — reflection-promoted skills are written under `/skills/`. */ getAgentDir(): string; /** Child sessions must not learn — the pass returns null for them. */ isChildSession(): boolean; /** Disposal short-circuits: no completion, no writes against a dead session. */ isDisposed(): boolean; /** Session-lifetime abort signal — aborts an in-flight reflection completion on dispose. */ getReflectionSignal(): AbortSignal; /** Archive a promoted skill (rollback of a `promote_skill` write). */ archivePromotedSkill(name: string): boolean; /** Make a just-written or archived skill visible to this session's skill vault. */ refreshLiveSkills?(): void; /** G3/G8 autonomy telemetry sink for learning-gate outcomes and approval requests. */ emitAutonomyTelemetry(event: AutonomyTelemetryEvent): void; /** Account the reflection pass's token spend into the cost roll-up (idempotent on reportId). */ addSpawnedUsage(usage: Usage, opts?: { label?: string; sourceSessionId?: string; reportId?: string; }): string | undefined; /** Persist a learning-gate decision snapshot to the session log. */ saveLearningDecisionSnapshot(decision: LearningDecision): string; /** Resolve text-tool fallback for the selected isolated model, not the foreground model. */ resolveTextToolCallProtocol(model: Model): SimpleStreamOptions["textToolCallProtocol"]; /** Ensure a managed-local model is running/resident before any isolated lane calls it. */ ensureModelReady(model: Model): Promise; /** * Session working directory — feeds the skill-overlap audit's project-local skill discovery, the * same `cwd` the model-invoked `skillify`/`skill_audit` tools already receive (tools/index.ts). * Optional so a host that has not wired it yet still resolves (falls back to `process.cwd()`, * mirroring the existing profile-resolution fallback in settings-manager.ts). */ getCwd?(): string; /** Already-loaded root skill universe; avoids a second unbounded filesystem discovery during reflection. */ getSkillsForAudit?(): readonly Skill[]; /** Root-owned durable version transition state; omitted by legacy or test hosts. */ getDurableLearningState?(): DurableLearningState | undefined; /** Installed runtime identity used only for root-owned durable transition detection. */ getRuntimeVersion?(): string | undefined; /** Versioned durable-memory interpretation policy. */ getMemoryPolicyVersion?(): string | undefined; /** Bounded host warning sink for fail-safe state degradation. */ warn?(message: string): void; } export declare const SKILL_OVERLAP_CONSOLIDATION_REASON_CODE = "skill_overlap_consolidation_proposed"; export declare const SKILL_AUDIT_UNAVAILABLE_REASON_CODE = "skill_audit_unavailable"; export declare const CURRENT_TURN_REFLECTION_CUSTOM_TYPE = "reflection_cue"; export declare const CURRENT_TURN_REFLECTION_STATE_CUSTOM_TYPE = "reflection_cue_state"; export type CurrentTurnReflectionTrigger = "root-turn" | "version-change" | Exclude; export interface CurrentTurnReflectionCueState { version: 1; revision: number; cueId: string; status: "pending" | "consumed" | "dismissed"; triggers: CurrentTurnReflectionTrigger[]; createdAt: string; updatedAt: string; /** Provider-hidden token proving exact ownership of one version transition. */ versionChange?: { token: DurableLearningClaimToken; metadata: DurableLearningReviewMetadata; }; /** Provider-hidden identity of the currently accepted cue-bearing request. */ activeRunToken?: string; } export interface CurrentTurnReflectionCuePlan { message: AgentMessage; isCurrent(): boolean; commit(): void; } export declare class ReflectionController { private readonly deps; private readonly unsubscribeSettingsChanges; private readonly durableLearningOwnerId; private readonly emittedWarningCodes; private cueStateCacheInitialized; private cueStateCache; private activeRunToken; constructor(deps: ReflectionControllerDeps); dispose(): void; private isAutomaticReflectionEnabled; /** Latest durable cue snapshot on the active session branch; custom state never enters model context. */ getCurrentTurnCueState(): CurrentTurnReflectionCueState | undefined; /** Branch/session navigation invalidates the lazy latest-state index; ordinary appends do not. */ invalidateCurrentTurnCueStateCache(options?: { releaseActiveClaim?: boolean; }): void; private persistCurrentTurnCueState; private warnOnce; private dismissCurrentTurnCue; private synchronizeCurrentTurnCueWithSettings; private attachCurrentTurnCue; /** Persist one logical ordinary cue without exposing claim metadata to callers. */ queueCurrentTurnCue(trigger: CurrentTurnReflectionTrigger): boolean; /** * Root-turn entrypoint: reconcile installed runtime/policy state and atomically attach any exact * version claim to the same provider cue. Fail-safe state faults degrade to an ordinary root cue. */ queueExternalRootTurnCue(): DurableLearningCueAttachOutcome; /** * Preview one transient provider-only cue. An accepted cue remains visible across provider-tool * continuation requests until AgentSession reports one strict terminal success or failure. */ previewCurrentTurnCue(): CurrentTurnReflectionCuePlan | undefined; /** Settle one accepted cue-bearing provider run at its authoritative AgentSession boundary. */ finishCurrentTurnCue(messages: AgentMessage[], options: { willRetry: boolean; }): void; /** Pure completed-turn projection used only by deterministic memory synchronization. */ analyzeCompletedTurn(messages: AgentMessage[]): ReflectionTurnAnalysis; /** * Run an explicit LLM completion fully ISOLATED from the main session for bounded host-owned * consumers. Automatic reflection never reaches this primitive. * * Isolation invariants (audited by codex): builds fresh context (no main history), defaults to no * tools, and passes **no real `sessionId`** — only a deterministic SYNTHETIC cache-affinity key * (see {@link computeLaneAffinityKey}) derived from `(laneKind, model, systemPrompt)`, which can never * equal or embed the real session id. A tool-enabled call receives only caller-owned tools and hooks, * and applies a turn bound only when its owner explicitly supplies one. It cannot mutate `agent.state.messages`, append session entries, or touch the * foreground tool registry. Mirrors `generateSummary()`'s one-shot mechanics otherwise. * * Returns the result even on an error/aborted stop reason; the explicit caller decides whether to * act, and a model-level error is returned rather than thrown. */ runIsolatedCompletion(opts: IsolatedCompletionOptions): Promise; /** * Explicit compatibility/application seam for an owner-invoked structured reflection pass. This * method can make an isolated provider request, so automatic current-turn reflection never calls * it. Demand-gates (zero-I/O), applies resulting writes through the bundled `memory` tool, and * accounts the explicitly requested pass through the spawned-usage surface. * * Returns `null` when the gate skips (or in a child session, which must not learn). The whole pass * is best-effort: a model/parse error yields no writes, never throws into the caller. */ runReflectionPass(input: { signals: DemandSignals; recentTurnText: string; model?: Model; thinkingLevel?: ThinkingLevel; signal?: AbortSignal; /** Stable id so a duplicate scheduling/retry of the same pass can't double-count its cost. */ reportId?: string; /** True only when every turn in this pass explicitly asked Pi to remember durable information. */ explicitUserMemoryInstruction?: boolean; }): Promise; getLearningAuditRecords(): LearningAuditRecord[]; /** * Roll back one applied durable learning change by executing the inverse operation recorded in * its audit record (memory ops run through the same bundled memory-tool path as the original * apply; promoted skills are archived). Appends a linked "rollback" audit record on success so * the change history stays complete and a change cannot be rolled back twice. */ rollbackLearningWrite(auditId: string): Promise<{ ok: boolean; reason: string; }>; /** * Apply one reflection write through the bundled `memory` tool. `memory_replace`/`memory_remove` * don't carry a target file, so we try MEMORY.md first and fall back to USER.md when the substring * isn't found there. Never throws (reflection must never break a turn); returns whether the write * actually applied so callers that MUST know — rollback's once-only accounting — can react instead * of recording a success that never happened. */ private _applyReflectionWrite; /** * The same skill_audit overlap check the model-invoked `skillify` tool enforces * (tools/skillify.ts → tools/skill-audit.ts's `runSkillAudit`), run before an AUTOMATIC promotion * so the reflection engine can never silently write a near-duplicate SKILL.md. Reuses the audit * seam unchanged (never duplicated) against the same already-loaded skills universe skillify * compares a draft against. Returns a block reason when the draft overlaps an existing skill above * the audit's own similarity threshold, or when the audit itself fails (the promotion is held, not * written unaudited); undefined when clear to promote. Never throws — mirrors every other * best-effort check in this file. */ private _checkSkillPromotionOverlap; /** * R7: write a reflection-promoted skill as `/skills//SKILL.md` so it loads like any * user skill. Best-effort; never clobbers an existing (hand-authored) skill of the same name. The * overlap audit runs at the call site in {@link runReflectionPass} — a write only reaches here * once the draft has already cleared it. */ private _promoteReflectionSkill; } //# sourceMappingURL=reflection-controller.d.ts.map