import { Model, Api, Transport, SimpleStreamOptions, AssistantMessage } from '@earendil-works/pi-ai'; export { createAssistantMessageEventStream } from '@earendil-works/pi-ai'; import { StreamFn } from '@earendil-works/pi-agent-core'; /** * Shared host-adapter framework. * * An adapter maps a host's lifecycle events onto a MemFlywheel's hooks. Adapters * contain NO memory logic — they are pure event translation plus a real, * round-trippable install of the host-side wiring. * * The scribe contract below is structurally identical to @memflywheel/sdk's * `MemFlywheel`. It is declared here (not imported) so adapters build and test * independently of the SDK package: any object with these methods — including a * real `createMemFlywheel(...)` — satisfies `MemFlywheel` structurally. */ /** One host tool call folded into a turn (structural mirror of core's ExtractionToolCall). */ interface MemFlywheelToolCall { name: string; input?: unknown; output?: unknown; } /** A turn message in the shape core extraction expects. */ interface MemFlywheelMessage { role: "user" | "assistant"; text: string; /** Host tool calls made on this turn, folded into extraction as truncated text. */ toolCalls?: MemFlywheelToolCall[]; /** * Absolute time anchor for this turn (e.g. "2023-05-08"), when the host knows * the turn's wall-clock time. Forwarded to extraction so relative dates can be * resolved to occurred_on. Optional; absent means no anchor and no date guess. */ timestamp?: string; } /** The two recall segments core produces. */ interface MemFlywheelContext { /** STABLE memory rules — host merges into its systemPrompt (cache-friendly). */ systemPrompt: string; /** DYNAMIC index prelude, wrapped in , injected per turn. */ preludePrompt: string; /** Optional learned-skill prelude appended by the SDK when skill recall is configured. */ skillPreludePrompt?: string; enabled: boolean; } /** * The lifecycle surface an adapter drives. Structurally compatible with the * SDK's `MemFlywheel`; only the hooks adapters actually call are required. */ interface MemFlywheel$1 { onSessionStart(input: { sessionId: string; }): Promise; onPromptBuild(input: { sessionId: string; query?: string; }): Promise; onTurnEnd(input: { sessionId: string; messages: MemFlywheelMessage[]; }): Promise; onSessionEnd(input: { sessionId: string; }): Promise; onIdle(input?: { force?: boolean; }): Promise; } /** The canonical scribe hooks a host event can map to. */ type MemFlywheelHook = "onSessionStart" | "onPromptBuild" | "onTurnEnd" | "onSessionEnd" | "onIdle"; /** One host-event → scribe-hook mapping row (documentation + verification data). */ interface LifecycleMapping { /** The scribe hook this host event drives. */ hook: MemFlywheelHook; /** The host's native event/callback name. */ hostEvent: string; /** Human description of what the adapter does at this point. */ note: string; } /** A host adapter's lifecycle map keyed by scribe hook. */ type LifecycleMap = Readonly>>; /** Where the host keeps the config the adapter installs its wiring into. */ interface InstallTarget { /** Absolute path to the host config file the wiring is written into. */ configPath: string; } /** A single planned change to the host config (computed, not yet applied). */ interface InstallStep { kind: "create-config" | "add-wiring" | "update-wiring" | "noop"; configPath: string; description: string; } /** The full set of changes `install({ apply:false })` would make. */ interface InstallPlan { adapterId: string; configPath: string; steps: InstallStep[]; /** True when nothing needs to change (already installed and current). */ satisfied: boolean; } /** Result of actually applying an install plan. */ interface InstallResult { adapterId: string; configPath: string; applied: InstallStep[]; } /** Outcome of a real round-trip verification (write was read back correctly). */ interface VerifyResult { adapterId: string; ok: boolean; /** Empty when ok; otherwise the concrete reasons verification failed. */ problems: string[]; } /** A doctor finding for an installed (or mis-installed) adapter. */ interface DoctorFinding { code: "not-installed" | "stale-wiring" | "corrupt-config" | "ok"; message: string; } /** * The adapter contract. Every host adapter implements this. * * `install` ALWAYS plans first; with `apply:true` it then applies and re-reads. * `verify` performs a real round-trip: it reads the host config back from disk * and confirms the wiring is present and well-formed — it never trusts a write. */ interface HostAdapter { /** Stable identifier, e.g. "pi". */ readonly id: string; /** Host display name. */ readonly name: string; /** Host-event → scribe-hook lifecycle map (for docs + verification). */ readonly lifecycle: LifecycleMap; /** * The host's default config file (where the wiring marker is installed) as a * path relative to the user's home directory, e.g. ".pi/agent/settings.json". * Lets `connect ` resolve a target with no explicit `--config`. */ readonly defaultConfigRelPath?: string; /** * One-line note on how the host actually consumes the scribe. "best-effort" * hosts (no first-class plugin source) carry the caveat here. */ readonly integrationNote?: string; /** * Wire a scribe into a live host runtime. Returns a disposer that detaches all * listeners. Pure event translation — no memory logic. */ attach(scribe: MemFlywheel$1, host: HostRuntime): () => void; /** Compute the config changes needed to install the wiring (no writes). */ install(target: InstallTarget, opts?: { apply?: boolean; }): Promise; /** Read the host config back and confirm the wiring round-trips. */ verify(target: InstallTarget): Promise; /** Diagnose the installed state of this adapter. */ doctor(target: InstallTarget): Promise; } /** * Minimal event surface an adapter binds to. Concrete hosts expose richer * objects; adapters down-cast through this for `attach`. `on` returns an * unsubscribe function (Node EventEmitter-compatible shape is also accepted). */ interface HostRuntime { on(event: string, listener: (payload: unknown) => void): (() => void) | unknown; off?(event: string, listener: (payload: unknown) => void): void; } /** The current wiring schema version. Bumping this makes old wiring "stale". */ declare const WIRING_VERSION = 1; /** Key under which the wiring marker lives in a host config object. */ declare const WIRING_KEY = "memflywheel"; /** The marker an adapter writes into a host config to claim it is installed. */ interface WiringMarker { version: number; adapter: string; /** Ordered list of (hostEvent → hook) bindings, for verification. */ bindings: { hostEvent: string; hook: MemFlywheelHook; }[]; } /** Build the wiring marker for an adapter from its lifecycle map. */ declare function buildWiringMarker(adapter: HostAdapter): WiringMarker; /** Compare two markers for exact wiring equality (version + bindings). */ declare function markersEqual(a: WiringMarker | undefined, b: WiringMarker): boolean; /** Read and parse a host config file; `null` when absent, throws on corrupt JSON. */ declare function readHostConfig(configPath: string): Promise | null>; /** Read the wiring marker out of a host config object, if present and shaped. */ declare function readWiringMarker(config: Record | null): WiringMarker | undefined; /** Atomically write a host config object as pretty JSON (temp file + rename). */ declare function writeHostConfig(configPath: string, config: Record): Promise; /** * Compute the install plan for an adapter against a host config file. * Pure read; never writes. `satisfied` is true when the on-disk wiring already * matches the adapter's current marker exactly. */ declare function planInstall(adapter: HostAdapter, target: InstallTarget): Promise; /** * Apply an adapter's install: plan, then (if needed) merge the wiring marker * into the host config and write it atomically, preserving all other keys. */ declare function applyInstall(adapter: HostAdapter, target: InstallTarget): Promise; /** * Real round-trip verification: read the config back from disk and confirm the * wiring marker is present and exactly matches the adapter's current marker. * Never trusts an in-memory write — always re-reads. */ declare function verifyInstall(adapter: HostAdapter, target: InstallTarget): Promise; /** * Resolve the install target for an adapter. An explicit `configPath` always * wins; otherwise the adapter's `defaultConfigRelPath` is resolved under the * user's home directory. Throws when neither is available. */ declare function resolveInstallTarget(adapter: HostAdapter, configPath?: string): InstallTarget; /** Outcome of {@link connect}: the plan/result plus the verification round-trip. */ interface ConnectResult { adapterId: string; configPath: string; /** The plan (apply:false) or the applied result (apply:true). */ install: InstallPlan | InstallResult; /** Present only when `apply:true`: the real re-read-from-disk verification. */ verify?: VerifyResult; } /** * One-call install + verify. Resolves the target (explicit path or the * adapter's default), plans the wiring, optionally applies it, then — when * applied — re-reads from disk and verifies the marker round-trips. This is the * common installation primitive for host-specific adapter tooling. */ declare function connect(adapter: HostAdapter, opts?: { configPath?: string; apply?: boolean; }): Promise; /** Diagnose installed state by re-reading the config (shared doctor). */ declare function doctorInstall(adapter: HostAdapter, target: InstallTarget): Promise; /** * Per-adapter translation of a raw host payload into the arguments a scribe hook * needs. Adapters supply this; everything else (binding, disposal) is shared. */ interface HookTranslators { /** Pull the sessionId out of a session-start payload. */ sessionId(payload: unknown): string; /** Pull a sessionId for prompt-build; defaults to `sessionId`. */ promptSessionId?(payload: unknown): string; /** Pull the current user request / task for index-layer retrieval. */ promptQuery?(payload: unknown): string | undefined; /** Pull (sessionId, messages) out of a turn-end payload. */ turnEnd(payload: unknown): { sessionId: string; messages: MemFlywheelMessage[]; }; /** Pull a sessionId for session-end; defaults to `sessionId`. */ sessionEndSessionId?(payload: unknown): string; /** Map an idle payload to onIdle input; defaults to `{}`. */ idle?(payload: unknown): { force?: boolean; } | undefined; } /** * Bind a scribe's hooks to a host runtime using the adapter's lifecycle map and * translators. Returns a disposer that removes every listener. This is the only * place host events touch the scribe — pure translation, no memory logic. * * `onPromptBuild` returns a `MemFlywheelContext`; the host is expected to read it from * the listener's return value (hosts that need the result pass a payload with a * `respond` callback — see the per-host adapters). */ declare function bindLifecycle(scribe: MemFlywheel$1, host: HostRuntime, lifecycle: LifecycleMap, translators: HookTranslators): () => void; /** * Factory that assembles a `HostAdapter` from a host's lifecycle map and a set * of payload translators. All install/verify/doctor logic is shared (see * adapter.ts); per-host files only declare WHICH host event maps to WHICH scribe * hook and HOW to read the host's payload shape. */ interface AdapterSpec { id: string; name: string; lifecycle: LifecycleMap; translators: HookTranslators; /** Host config path relative to the home directory (for `connect `). */ defaultConfigRelPath?: string; /** One-line integration note (carries the "best-effort" caveat when needed). */ integrationNote?: string; } /** Build a fully-wired `HostAdapter` from a per-host spec. */ declare function makeAdapter(spec: AdapterSpec): HostAdapter; /** Read a string field from an unknown payload, or "" if absent. */ declare function readString(payload: unknown, key: string): string; /** * Normalize an arbitrary transcript array into MemFlywheelMessages: keep user/assistant * roles, coerce text, drop empties. When folding is enabled (default), tool calls * are folded into the assistant turn that made them, paired with their result — * supporting both the OpenAI shape (assistant `tool_calls` + `role:"tool"` replies) * and the Anthropic shape (`tool_use` / `tool_result` content blocks). The folded * tool text is later truncated by core's renderer (input 200 / output 500 head+tail * + window cap), so a huge tool output cannot bloat the extraction prompt. */ declare function normalizeMessages(raw: unknown): MemFlywheelMessage[]; /** * Domain types and constants for the MemFlywheel memory kernel. * * The persisted frontmatter carries `name` / `description` / `type`, optional * retrieval routing terms, and minimal write/event timestamps. The six memory * categories are the canonical VALID_MEMORY_TYPES. */ type MemoryType = "identity" | "preference" | "style" | "workflow" | "context" | "ambient"; type AuditAction = "write" | "delete" | "extract" | "dream-apply" | "relocate" | "archive" | "secret-refused"; interface AuditRecord { ts: string; action: AuditAction; path?: string; detail?: string; } interface AuditLogger { append(record: AuditRecord): Promise; } /** * Single-document CRUD over typed directories, atomic, with privacy enforcement * and audit records. */ interface StorageContext { root: string; audit: AuditLogger; } interface EmbeddingProvider { embed(input: { texts: string[]; signal?: AbortSignal; }): Promise<{ vectors: number[][]; }>; } /** * Two-segment recall injection. * * Segment 1 (systemPrompt): STABLE memory rules → cache-friendly prefix. * Segment 2 (preludePrompt): DYNAMIC index cues in . */ interface BuildContextResult { systemPrompt: string; preludePrompt: string; enabled: boolean; } type MemoryIndexRetrievalMode = "auto" | "off" | "required"; interface MemoryIndexRetrievalDiagnostic { stage: "skip" | "records" | "cache-start" | "cache-complete" | "search-start" | "search-complete" | "fallback"; reason?: string; mode?: MemoryIndexRetrievalMode; records?: number; selected?: number; selectedLineIds?: string[]; selectedPaths?: string[]; bytes?: number; limit?: number; minRecords?: number; errorName?: string; errorMessage?: string; errorCauseName?: string; errorCauseCode?: string; errorCauseMessage?: string; } interface MemoryIndexRetrievalOptions { mode?: MemoryIndexRetrievalMode; embeddingProvider?: EmbeddingProvider; model?: string; limit?: number; minRecords?: number; signal?: AbortSignal; onDiagnostic?: (event: MemoryIndexRetrievalDiagnostic) => void; } /** * Root-bound file tools for MemFlywheel subagents. * * Tool names and parameter shapes follow the ordinary agent file-tool * surface: read / write / edit / bash / glob / grep. Memory-specific rules stay * in the executor: typed memory writes are parsed, validated, privacy-checked, * atomically written, audited, and followed by MEMORY.md index sync. */ interface JsonSchema { type: "object"; properties: Record; required: string[]; additionalProperties: false; } type FileToolName = "read" | "write" | "edit" | "bash" | "glob" | "grep"; interface FileToolResult { ok: boolean; text: string; changed?: string[]; } interface FileToolContext { root: string; audit?: AuditLogger; mode?: "memory" | "files"; refuseSecrets?: boolean; sourceRef?: MemorySourceRef; afterMutation?: () => Promise; } interface MemorySourceRef { relativePath: string; absolutePath: string; startLine: number; endLine: number; } interface FileTool { name: FileToolName; description: string; inputSchema: JsonSchema; handler: (args: unknown, toolCtx: FileToolContext) => Promise; } /** * Extraction kernel: session closure around an injected subagent runner. * * Core owns the full extraction lifecycle (lock / window / relocate / index / * cursor) but NEVER calls an LLM. The actual memory writes are performed by the * injected ExtractionAgentRunner, which drives a tool-calling subagent that * writes files directly through ordinary file tools. */ declare enum ExtractionResult { Completed = "completed", Skipped = "skipped", Failed = "failed" } /** * One host tool call, folded into the extraction context as text. The host * adapter pairs a tool invocation with its result; core renders them as * truncated `Tool(name): input` / `Output: output` lines so durable facts that * only surface in tool activity (e.g. "this project uses pnpm") are not lost. * MemFlywheel cannot fork the host agent (it is an external scribe), so it cannot * share the host's structured tool blocks / prompt cache the way an in-host * extractor can — instead it folds tool calls into the reconstructed transcript. */ interface ExtractionToolCall { /** Tool name, e.g. "Bash", "Read". */ name: string; /** Tool input/arguments (any JSON-serializable value). */ input?: unknown; /** Tool result/output (any JSON-serializable value or string). */ output?: unknown; } interface ExtractionMessage { role: "user" | "assistant"; text: string; /** * Host tool calls made on this turn (assistant turns), folded into the * extraction context as truncated text. Optional and backward-compatible: a * message with no toolCalls renders exactly as before. */ toolCalls?: ExtractionToolCall[]; /** * Absolute time anchor for THIS turn (e.g. "2023-05-08" or an ISO datetime), * supplied by the host when the turn's wall-clock time is known. It lets the * extractor resolve relative dates in the text ("yesterday", "last week") * into an absolute `occurred_on`. Optional and backward-compatible: when * absent the message renders exactly as before and no date is ever guessed. */ timestamp?: string; } /** * THE pluggable injection point. The SDK supplies a tool-calling agent loop * here; core calls it inside the held write lock. The runner writes memories * itself via the supplied tools (bound to the same context, so they share the * lock). Core never calls an LLM — it calls this. */ type ExtractionAgentRunner = (input: { toolCtx: FileToolContext; tools: FileTool[]; messages: ExtractionMessage[]; manifest: string; root: string; }) => Promise<{ changed: string[]; }>; interface CursorStore { get(sessionId: string): number | null; set(sessionId: string, cursorIndex: number): void; } /** * Structural health findings used by dream's deterministic planner and host * diagnostics. */ type HealthCode = "missing-frontmatter" | "missing-frontmatter-name" | "missing-frontmatter-type" | "invalid-frontmatter-type" | "path-type-mismatch" | "duplicate-name-type" | "duplicate-content"; interface HealthFinding { severity: "error" | "warn"; code: HealthCode; paths: string[]; message: string; } interface TypeReviewItem { path: string; type: string; name: string; description: string; excerpt: string; } /** * Dream consolidation: a deterministic structural pre-pass + a tool-calling * consolidation subagent. * * Two phases, mirroring extraction's "subagent writes files directly" model: * 1. Deterministic pre-pass (no LLM): the unambiguous structural fixes — * identical-body duplicates are removed, files sitting in the wrong type * directory are relocated. Safe, fast, and LLM-free. * 2. Semantic consolidation (subagent): the injected DreamAgentRunner reads the * health / type-review packets, READS FULL BODIES, and merges / compresses / * retires memories by calling the same ordinary file tools the extraction subagent * uses. It never authors a merged body from a truncated excerpt — it reads * first, exactly as read-before-update protects list-type appends. * * `runDreamSession` owns the session closure: it holds the per-root write lock * across both phases, applies the deterministic ops, invokes the subagent under * the same lock, relocates stray root files, and resyncs the index. */ /** A consolidation directive a host can pass to bias the subagent (optional). */ interface DreamCoordination { reason: string; memoryAction: string; topics: string[]; targetSkill?: string; } /** * THE pluggable dream injection point — symmetric to ExtractionAgentRunner. The * subagent receives the structural packets plus the bound ordinary file tools/context * (sharing the held write lock) and consolidates by calling those tools * directly. It returns the union of relative paths it changed. */ type DreamAgentRunner = (input: { root: string; toolCtx: FileToolContext; tools: FileTool[]; health: HealthFinding[]; typeReview: TypeReviewItem[]; manifest: string; index: string; coordination?: DreamCoordination; }) => Promise<{ changed: string[]; }>; interface PiAgentModelBinding { model: Model; streamFn: StreamFn; getApiKey?: (provider: string) => Promise | string | undefined; sessionId?: string; thinkingLevel?: "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max"; transport?: Transport; /** Host-resolved request context forwarded unchanged on every provider turn. */ request?: SimpleStreamOptions & Record; } type ResolvePiAgentModel = () => PiAgentModelBinding | Promise; /** * Learned-skill evolution runner. * * This mirrors extraction/dream at the SDK layer: the SDK owns the model loop and * the skill package owns file mutations through its tools. The runner wraps the * pass in a skill checkpoint, finalizes the staged file changes, derives skill * coordination from the resulting diff, then hard-validates that the coordination * and actual changed skills agree. */ type SkillEvolutionDecision = "create" | "update" | "merge" | "noop"; type SkillEvolutionMemoryAction = "compress-memory" | "noop"; interface SkillEvolutionCoordination { decision: SkillEvolutionDecision; targetSkill: string | null; mergedSkills: string[]; why: string; memoryAction: SkillEvolutionMemoryAction; memoryTopics: string[]; supportingFiles: string[]; } /** * SDK learning-loop orchestration. * * The host supplies concrete extraction, skill-evolution, and dream callbacks. * This module owns only the ordering and gates: * * turn-end -> extraction -> skill learning -> dream coordination * error -> extraction only * inactive-flush -> skill review only */ type LearningLoopSource = "local" | "remote"; interface SkillLearningGate { minDoneTurns: number; cooldownTurns: number; minToolCalls: number; } interface SkillEvolutionLoopResult { coordination: SkillEvolutionCoordination; changedSkills: string[]; changedFiles: string[]; } interface LearningLoopStepResult { ran: boolean; reason: string; value?: T; } interface LearningLoopResult { extraction: LearningLoopStepResult; skillEvolution: LearningLoopStepResult; dream: LearningLoopStepResult; } /** * @memflywheel/sdk — host lifecycle integration layer. * * This is the thin orchestration seam between host runtimes such as Pi, Hermes, * OpenClaw, OpenCode, and @memflywheel/core. It owns: * * - a single per-root StorageContext + audit logger, * - the per-session extraction cursor store, * - the TWO pluggable LLM injection points (agent, dreamRunner), * - the host lifecycle hooks that decide *when* core runs. * * The scribe itself NEVER calls an LLM. After-turn extraction follows the file- * native after-turn flow (lock → relocate → before-scan → cursor window → * extraction subagent → relocate → after-scan → syncIndex → advance cursor on * success → release → drain queue); the only difference is the LLM-driven write * is externalized to the host-provided extraction `agent`. That agent is a * tool-calling loop: it calls ordinary file tools, which WRITE FILES directly. * Dream consolidation is the same kind of subagent over the same channel. The * SDK ships the extraction / dream / skill loops over a provider-neutral * host-resolved pi-ai model binding. Providers and host runtimes live outside the SDK. * * The host gathers the conversation turn into ExtractionMessage[] and calls the * hooks; core does the rest (write lock, atomic writes, index sync, cursor). */ interface SkillRecallEntry { name: string; displayName: string; description: string; relativePath: string; triggerHints?: string[]; } interface SkillRecallPacket { entries: SkillRecallEntry[]; } type SkillRecallProvider = (input: { sessionId?: string; }) => Promise; type SkillPreludeBuilder = (packet: SkillRecallPacket) => string; interface MemFlywheelLearningLoopConfig { enabled?: boolean; source?: LearningLoopSource; skillLearningEnabled?: boolean; gate?: Partial; /** * Optional host override for the learning gate. When omitted, the SDK counts * tool calls from the session's captured ExtractionMessage.toolCalls. */ toolCalls?: number | (() => number); /** * Optional host override for the learning cooldown gate. When omitted, the SDK * tracks the turn number of the previous skill-evolution pass per session. */ turnsSinceLastSkillEvolution?: number | (() => number); skillEvolution?: (input: { sessionId: string; lastExtraction: TurnEndResult; session: SessionState; }) => Promise; } interface MemFlywheelBuildContextResult extends BuildContextResult { skillPreludePrompt?: string; } interface PromptBuildInput { sessionId?: string; query?: string; } /** What a single session collects between session-start and turn-ends. */ interface SessionState { sessionId: string; /** All turn messages seen so far, in order. The extraction cursor indexes into this. */ messages: ExtractionMessage[]; /** Number of turns ended in this session (an after-turn extraction per turn). */ turns: number; } /** Result of an after-turn extraction pass surfaced to the host. */ interface TurnEndResult { result: ExtractionResult; /** True when the scribe is disabled or no extraction agent is configured. */ skipped: boolean; /** Present when createMemFlywheel owns the turn-end learning loop. */ learningLoop?: LearningLoopResult; } /** Result of a dream pass surfaced to the host. */ interface DreamRunResult { ran: boolean; /** Why dream did/did not run: "disabled" | "gate-not-met" | "locked" | "ok" | "runner-failed". */ reason: string; /** Relative paths changed across the deterministic pre-pass + subagent (when it ran). */ changed?: string[]; /** Relative paths deleted by the deterministic pre-pass (when it ran). */ deleted?: string[]; } /** Options for an explicit, host-triggered memory write. */ interface SaveOptions { type: MemoryType; name: string; description?: string; body: string; /** ADD-only override: archive these relativePaths first (explicit user correction). */ archives?: string[]; } /** Gate inputs for onIdle (auto-dream). Mirrors core.shouldRunDream. */ interface DreamGateInput { now?: number; lastConsolidatedAt?: number | null; candidateSessionCount?: number; minHours?: number; minSessions?: number; force?: boolean; coordination?: DreamCoordination; } /** The host-facing memory scribe. */ interface MemFlywheel { readonly root: string; readonly enabled: boolean; readonly ctx: StorageContext; /** Host session began. Ensures the memory dir exists and registers session state. */ onSessionStart(sessionId: string): Promise; /** A new turn began (host about to build the prompt). Registers session state. */ onTurnStart(sessionId: string): void; /** * Host is assembling the prompt. Returns the two recall segments: * - systemPrompt: STABLE memory rules (cache-friendly prefix) * - preludePrompt: DYNAMIC index cues wrapped in */ onPromptBuild(input?: PromptBuildInput): Promise; /** * A turn finished. The host passes the turn's user+assistant messages; the SDK * appends them to session state and runs after-turn extraction via the * injected agent (the file-native after-turn extraction flow). */ onTurnEnd(sessionId: string, turnMessages: ExtractionMessage[]): Promise; /** Host session ended. Drops session state. (Extraction already ran per-turn.) */ onSessionEnd(sessionId: string): Promise; /** * The auxiliary/agent run ended (host-level, distinct from a chat turn). * Runs a final extraction over any not-yet-processed messages for the session. */ onAgentEnd(sessionId: string): Promise; /** * Idle / scheduled consolidation. Gate-checked (time OR session-count), then * runs runDreamSession under the write lock: the deterministic structural * pre-pass, then the consolidation subagent (when a dreamRunner is configured). */ onIdle(opts?: DreamGateInput): Promise; /** Return the default index prelude and stable memory rules. */ context(): Promise; /** Explicit, validated memory write (under lock, syncs index). */ save(options: SaveOptions): Promise; /** Force a dream pass regardless of gate. */ runDream(coordination?: DreamCoordination): Promise; /** The stable memory-rules system prompt (constant; cache-friendly). */ instructionPrompt(): string; /** Snapshot a session's collected state (or undefined if unknown). */ getSession(sessionId: string): SessionState | undefined; } interface HostToolCall { id: string; name: string; input: unknown; } interface HostMessage { role: "user" | "assistant" | "tool"; content?: string | null; toolCalls?: HostToolCall[]; toolCallId?: string; } type HostCapability = "prompt-build" | "turn-end" | "session-end" | "idle" | "agentic-tool-loop" | "tool-trajectory"; type HostIntegrationMode = "none" | "recall-only" | "memory-loop" | "skill-loop"; type Dispose = () => void; interface HostPromptBuildEvent { sessionId?: string; query?: string; } interface HostPromptBuildResult { systemPrompt?: string; preludePrompt?: string; skillPreludePrompt?: string; } interface HostTurnEndEvent { sessionId: string; messages: HostMessage[]; } interface HostSessionEvent { sessionId: string; } interface HostIdleEvent { force?: boolean; } interface HostLifecyclePort { onPromptBuild(handler: (event: HostPromptBuildEvent) => Promise): Dispose; onTurnEnd(handler: (event: HostTurnEndEvent) => Promise): Dispose; onSessionEnd(handler: (event: HostSessionEvent) => Promise): Dispose; onIdle?(handler: (event?: HostIdleEvent) => Promise): Dispose; } interface HostToolCallEvent { sessionId?: string; toolCallId: string; toolName: string; input: unknown; } interface HostToolResultEvent extends HostToolCallEvent { output: unknown; isError?: boolean; } interface HostTelemetryPort { onToolCall?(handler: (event: HostToolCallEvent) => Promise): Dispose; onToolResult?(handler: (event: HostToolResultEvent) => Promise): Dispose; } interface HostHarnessPort { readonly name: string; readonly capabilities: ReadonlySet; readonly lifecycle: HostLifecyclePort; readonly resolveModel: ResolvePiAgentModel; readonly telemetry?: HostTelemetryPort; } declare function classifyHostCapabilities(capabilities: ReadonlySet): HostIntegrationMode; declare function requireHostCapabilities(hostName: string, capabilities: ReadonlySet, required: readonly HostCapability[]): void; declare function createCapabilitySet(capabilities: readonly HostCapability[]): ReadonlySet; type PiDispose = Dispose | void; interface PiTextContent { type: "text"; text: string; } interface PiImageContent { type: "image"; [key: string]: unknown; } interface PiToolCallContent { type: "toolCall"; id: string; name: string; arguments: Record; } interface PiUserMessage { role: "user"; content: string | Array; timestamp?: number; } interface PiToolResultMessage { role: "toolResult"; toolCallId: string; toolName: string; content: Array; isError?: boolean; timestamp?: number; } type PiAssistantMessage = AssistantMessage; type PiAgentMessage = PiUserMessage | PiAssistantMessage | PiToolResultMessage; interface PiModelAuthResult { ok: boolean; apiKey?: string; headers?: Record; error?: string; } interface PiExtensionContextLike { mode?: "tui" | "rpc" | "json" | "print"; cwd?: string; model?: unknown; signal?: AbortSignal; sessionManager?: { getSessionId?(): string; }; modelRegistry?: { getApiKeyAndHeaders?(model: unknown): Promise; }; getThinkingLevel?(): unknown; isIdle?(): boolean; } type PiExtensionHandler = (event: unknown, ctx?: PiExtensionContextLike) => unknown | Promise; interface PiExtensionApiLike { on(event: string, handler: PiExtensionHandler): PiDispose; off?(event: string, handler: PiExtensionHandler): void; } type PiStreamSimple = PiAgentModelBinding["streamFn"]; type PiSessionIdResolver = string | ((input: { event?: unknown; context?: PiExtensionContextLike; }) => string | undefined); type PiLifecycleAfterHook = () => void | Promise; interface CreatePiAgentModelResolverOptions { streamSimple: PiStreamSimple; /** Explicit Pi model; when absent, the current ExtensionContext model is used. */ model?: unknown; /** Latest Pi ExtensionContext, captured by lifecycle events. */ getContext?: () => PiExtensionContextLike | undefined; /** Optional stable session id used for provider/session affinity. */ getSessionId?: () => string | undefined; } interface CreatePiHarnessPortOptions { resolveModel?: ResolvePiAgentModel; /** Use Pi's native streamSimple(model, context, options) function. */ streamSimple?: PiStreamSimple; /** Explicit Pi model for background MemFlywheel loops. Defaults to ctx.model. */ piModel?: unknown; /** Resolve the MemFlywheel session id from Pi event/context. Defaults to Pi session id, else "pi". */ sessionId?: PiSessionIdResolver; /** * Optional idle polling. Pi exposes ctx.isIdle(), not an idle event; enabling * this opts into a real polling bridge instead of claiming a non-existent hook. */ idleIntervalMs?: number; afterPromptBuild?: PiLifecycleAfterHook; afterTurnEnd?: PiLifecycleAfterHook; afterSessionEnd?: PiLifecycleAfterHook; } interface PiScribeLike { onSessionStart(input: { sessionId: string; }): Promise; onPromptBuild(input: { sessionId: string; query?: string; }): Promise<{ systemPrompt?: string; preludePrompt?: string; skillPreludePrompt?: string; enabled?: boolean; }>; onTurnEnd(input: { sessionId: string; messages: MemFlywheelMessage[]; }): Promise; onSessionEnd(input: { sessionId: string; }): Promise; onIdle?(input?: { force?: boolean; }): Promise; } declare function hostMessagesFromPi(messages: unknown): HostMessage[]; declare function memScribeMessagesFromPi(messages: unknown): MemFlywheelMessage[]; declare function buildPiPromptInjection(result: HostPromptBuildResult): string; declare function createPiAgentModelResolver(options: CreatePiAgentModelResolverOptions): ResolvePiAgentModel; declare function attachPiScribe(scribe: PiScribeLike, pi: PiExtensionApiLike, options?: { sessionId?: PiSessionIdResolver; }): Dispose; declare function createPiHarnessPort(pi: PiExtensionApiLike, options?: CreatePiHarnessPortOptions): HostHarnessPort; type RawRecord = Record; interface OpenCodeClientLike { readonly session?: { readonly messages?: (options: unknown) => Promise; }; } interface OpenCodePluginInput { readonly client?: OpenCodeClientLike; } interface OpenCodeHarnessPortOptions { readonly root?: string; readonly resolveModel?: ResolvePiAgentModel; readonly messageLimit?: number; } interface OpenCodeHooks { readonly dispose?: () => Promise | void; readonly config: (config: { permission?: unknown; }) => Promise; readonly event: (input: { readonly event: unknown; }) => Promise; readonly "chat.message": (input: { readonly sessionID: string; readonly model?: { readonly providerID?: string; readonly modelID?: string; }; }, output: unknown) => Promise; readonly "chat.params": (input: { readonly sessionID: string; readonly model: unknown; readonly provider: unknown; }, output: { readonly temperature?: number; readonly maxOutputTokens?: number; readonly options?: RawRecord; }) => Promise; readonly "experimental.chat.system.transform": (input: { readonly sessionID?: string; }, output: { system: string[]; }) => Promise; readonly "experimental.text.complete": (input: { readonly sessionID: string; readonly messageID: string; readonly partID: string; }, output: { readonly text: string; }) => Promise; readonly "tool.execute.before": (input: { readonly tool: string; readonly sessionID: string; readonly callID: string; }, output: { readonly args: unknown; }) => Promise; readonly "tool.execute.after": (input: { readonly tool: string; readonly sessionID: string; readonly callID: string; readonly args: unknown; }, output: { readonly output?: string; readonly title?: string; readonly metadata?: unknown; }) => Promise; } declare function defaultOpenCodeMemFlywheelRoot(env?: NodeJS.ProcessEnv): string; /** Allow every OpenCode session to progressively read the active MemFlywheel store. */ declare function configureOpenCodeMemoryPermission(config: { permission?: unknown; }, root: string): void; /** Build a pi-ai completion from OpenCode's resolved model and credential context. */ declare function createOpenCodeHostModel(input: { readonly model: unknown; readonly provider: unknown; }, output: { readonly temperature?: number; readonly maxOutputTokens?: number; readonly options?: RawRecord; }): PiAgentModelBinding; declare function hostMessagesFromOpenCodeSessionMessages(raw: unknown): HostMessage[]; declare function createOpenCodeHarnessPort(client: OpenCodeClientLike, options?: OpenCodeHarnessPortOptions): HostHarnessPort & { readonly hooks: OpenCodeHooks; }; declare function createOpenCodePluginServer(input: OpenCodePluginInput, options?: OpenCodeHarnessPortOptions): OpenCodeHooks; interface OpenClawNativeModelSelection { readonly agentId?: string; readonly modelRef?: string; } interface OpenClawNativeModelRuntime { readonly currentConfig: () => unknown; readonly resolveDefaultAgentId: (config: unknown) => string; readonly prepareForAgent: (input: { readonly cfg: unknown; readonly agentId: string; readonly modelRef?: string; }) => Promise; readonly completePrepared: (input: { readonly model: unknown; readonly auth: unknown; readonly context: unknown; readonly cfg: unknown; readonly options: { readonly signal?: AbortSignal; }; }) => Promise; } /** Bind OpenClaw's native model/auth transport to the single Pi Agent Core runner. */ declare function createOpenClawHostModel(runtime: OpenClawNativeModelRuntime, selection: () => OpenClawNativeModelSelection): ResolvePiAgentModel; type OpenClawHookHandler = (event: unknown, context?: unknown) => Promise | unknown; interface OpenClawApiLike { readonly on?: (event: string, handler: OpenClawHookHandler, opts?: unknown) => void; readonly registerHook?: (events: string | readonly string[], handler: OpenClawHookHandler, opts?: unknown) => void; readonly registerMemoryCapability?: (capability: unknown) => void; readonly logger?: { error(message: string): void; }; } declare function openClawHostMemoryPaths(config: unknown): string[]; declare function registerOpenClawSingleWriterGuard(api: OpenClawApiLike, root: string, protectedMemoryPaths?: readonly string[]): void; interface OpenClawHarnessPortOptions { readonly root?: string; readonly protectedMemoryPaths?: readonly string[]; readonly resolveModel?: ResolvePiAgentModel; readonly nativeModelRuntime?: OpenClawNativeModelRuntime; } declare function hostMessagesFromOpenClawMessages(raw: unknown): HostMessage[]; declare function defaultOpenClawMemFlywheelRoot(env?: NodeJS.ProcessEnv): string; declare function createOpenClawHarnessPort(api: OpenClawApiLike, options?: OpenClawHarnessPortOptions): HostHarnessPort; declare function registerOpenClawMemoryCapability(api: OpenClawApiLike): void; declare function createOpenClawPluginRuntime(api: OpenClawApiLike, options?: OpenClawHarnessPortOptions): Dispose; /** * Host harness runtime — turn a host-owned pi-ai model binding into a * fully-wired memory scribe the adapters can drive directly. * * Both memory subagents are tool-calling loops: the SDK ships * `createExtractionAgentRunner({ model })` and `createDreamAgentRunner({ model })`, * loops that call core's memory-write tools to write files directly. The only * model contract is Pi's native Model + StreamFn; provider wire shapes stay in * the host adapters. * * Nothing here owns provider auth or performs model transport by itself. The * host resolves the active model binding, usually via a HostHarnessPort. */ interface HostLearnedSkillEvolutionInput { sessionId: string; lastExtraction: TurnEndResult; session: SessionState; } interface HostLearnedSkillsOptions { /** Directory where learned skills are finalized as file-native packages. */ skillsRoot: string; /** Directory for staged skill checkpoints. Defaults to /.checkpoints. */ checkpointRoot?: string; /** Public-name residues rejected from generated skill text. */ forbiddenPublicNames?: readonly string[]; /** Include current skill content in the skill-evolution prompt. */ includeSkillContent?: boolean; /** Override the skill-evolution system prompt. */ systemPrompt?: string; /** Max tool-calling rounds for the skill-evolution subagent. */ maxSteps?: number; /** Build the review packet sent to the skill-evolution subagent. */ reviewPacket?: (input: HostLearnedSkillEvolutionInput) => unknown; /** Build the tool trajectory sent to the skill-evolution subagent. */ toolTrajectory?: (input: HostLearnedSkillEvolutionInput) => unknown; /** Build artifact path hints sent to the skill-evolution subagent. */ artifactPaths?: (input: HostLearnedSkillEvolutionInput) => string[]; /** Build quality signals sent to the skill-evolution subagent. */ qualitySignals?: (input: HostLearnedSkillEvolutionInput) => unknown; } type MemFlywheelHarnessMode = "native" | "recall-only"; /** Options for {@link createMemFlywheelHarnessRuntime}. */ interface MemFlywheelHarnessRuntimeOptions { /** * Optional host port. The runtime uses its model resolver and lifecycle * binding remains explicit so existing adapter attach tests stay focused. */ port?: HostHarnessPort; /** * Host-owned pi-ai model resolver. Drives extraction, dream, and skill evolution. */ resolveModel?: ResolvePiAgentModel; /** Explicit runtime mode. No implicit recall-only fallback. */ mode?: MemFlywheelHarnessMode; /** Memory root override. Falls back to MEMFLYWHEEL_HOME / OS data dir. */ root?: string; /** Custom cursor store. Defaults to the SDK in-memory cursor store. */ cursorStore?: CursorStore; /** Master switch. When false, every hook becomes a no-op. */ enabled?: boolean; /** * Hard secret gate for the memory write tools. Default OFF — privacy leans on * the extraction prompt. `` redaction is always on regardless. */ refuseSecrets?: boolean; /** * Provide an extraction agent explicitly instead of building one from `model`. * Takes precedence over `model` for extraction. */ agent?: ExtractionAgentRunner; /** * Provide a dream consolidation subagent explicitly. Defaults to one built from * `model`; pass `null` to disable semantic consolidation * (deterministic structural pre-pass only). */ dreamRunner?: DreamAgentRunner | null; /** Optional learned-skill recall source used during prompt build. */ skillRecall?: SkillRecallProvider; /** Optional renderer for learned-skill recall packets. Defaults to the SDK renderer. */ skillPreludeBuilder?: SkillPreludeBuilder; /** Optional turn-end learning loop. When set, onTurnEnd runs extraction -> skill -> dream. */ learningLoop?: MemFlywheelLearningLoopConfig; /** Optional MEMORY.md index-layer hybrid retrieval. Host owns embedding/auth. */ memoryIndexRetrieval?: MemoryIndexRetrievalOptions; /** * Opt-in learned-skill assembly. When set with `model`, the bridge creates a * file-native learned-skill store, recall provider, and * skill-evolution runner. Hosts may still override `learningLoop` gates or * packet builders, but no custom callback is required for the closed path. */ learnedSkills?: HostLearnedSkillsOptions; } /** * Adapter-ready scribe returned by {@link createMemFlywheelHarnessRuntime}. It * satisfies the adapter lifecycle contract. */ interface MemFlywheelHarnessRuntimeAdapter extends MemFlywheel$1 { onTurnEnd(input: { sessionId: string; messages: MemFlywheelMessage[]; }): Promise; } /** The result of {@link createMemFlywheelHarnessRuntime}. */ interface MemFlywheelHarnessRuntime { /** The adapter-facing scribe — pass straight to `adapter.attach(scribe, host)`. */ scribe: MemFlywheelHarnessRuntimeAdapter; /** The underlying SDK scribe, for explicit ops (context/save/runDream). */ sdk: MemFlywheel; /** Runtime mode after capability/options resolution. */ mode: HostIntegrationMode | MemFlywheelHarnessMode; /** Detach host lifecycle listeners created from `port`, when any. */ dispose: () => void; } declare function hostMessagesToMemFlywheelMessages(messages: readonly HostMessage[]): MemFlywheelMessage[]; declare function attachMemFlywheelToHostPort(scribe: MemFlywheelHarnessRuntimeAdapter, port: HostHarnessPort): () => void; /** * Adapt an SDK `MemFlywheel` (hooks take positional args, onPromptBuild returns a * BuildContextResult) to the adapter-facing `MemFlywheel` (hooks take a single * payload object). The two recall segments are structurally identical, so the * `MemFlywheelContext` passes through unchanged. `onAgentEnd` is folded into * `onSessionEnd` so the adapter lifecycle's session-end runs a final sweep over * any not-yet-extracted messages before dropping the session. */ declare function adaptSdkMemFlywheel(sdk: MemFlywheel): MemFlywheelHarnessRuntimeAdapter; /** * Build a batteries-included scribe from a host's active pi-ai model binding. * * - With `model`: real semantic extraction + consolidation run as * tool-calling subagents on the host's own model, writing memory files directly. * - Without `model` and without an explicit `agent`: pass `mode:"recall-only"` * explicitly, or construction fails. */ declare function createMemFlywheelHarnessRuntime(options?: MemFlywheelHarnessRuntimeOptions): MemFlywheelHarnessRuntime; /** * Pi adapter — the Pi kernel (real integration). * * Pi loads top-level `.js` extensions from an extensions directory plus a * `settings.json` `extensions` array. An extension module receives the Pi * ExtensionAPI and binds its per-session hooks; this adapter maps those onto the * scribe: * * - `session_start` → onSessionStart * - `context` → onPromptBuild, returning `{ messages }` to prepend recall * - `agent_end` → onTurnEnd * - `session_shutdown` → onSessionEnd * * `createPiHarnessPort` maps Pi's native model/lifecycle/telemetry surface into * the canonical HostHarnessPort. The extraction subagent then runs on Pi's own * model and writes memory files directly via ordinary file tools. */ declare const piAdapter: HostAdapter; /** * Hermes adapter (real integration). * * A Hermes plugin's `register(ctx)` exposes its model transport as a pi-ai * StreamFn and binds the scribe to Hermes' real hooks: * * - `on_session_start` → onSessionStart * - `pre_llm_call` → onPromptBuild (inject prelude as {"context": ...} into * the user message; merge systemPrompt once at session * start to preserve the prompt-cache prefix) * - `post_llm_call` → onTurnEnd (fire-and-forget extraction; fires after * the tool loop completes, transcript is final) * - `on_session_end` → onIdle (per-turn end point; gate-checked dream) * * See examples/hermes for the `register(ctx)` glue that requires * `ctx.llm.completeWithTools`. */ declare const hermesAdapter: HostAdapter; /** * OpenCode adapter. * * - chat/system transform → onPromptBuild * - session.idle + messages API → onTurnEnd * - session.deleted → onSessionEnd */ declare const opencodeAdapter: HostAdapter; /** * OpenClaw adapter. * * Lifecycle: * - `before_prompt_build` → onPromptBuild * - `agent_end` → onTurnEnd * - `session_end` → session flush in the native plugin port */ declare const openclawAdapter: HostAdapter; /** Registry of all built-in host adapters, keyed by id. */ /** Every built-in adapter. */ declare const ADAPTERS: readonly HostAdapter[]; /** Look up an adapter by its id; `undefined` when unknown. */ declare function getAdapter(id: string): HostAdapter | undefined; /** All known adapter ids. */ declare function adapterIds(): string[]; export { ADAPTERS, type AdapterSpec, type ConnectResult, type CreatePiAgentModelResolverOptions, type CreatePiHarnessPortOptions, type Dispose, type DoctorFinding, type HookTranslators, type HostAdapter, type HostCapability, type HostHarnessPort, type HostIdleEvent, type HostIntegrationMode, type HostLearnedSkillEvolutionInput, type HostLearnedSkillsOptions, type HostLifecyclePort, type HostMessage, type HostPromptBuildEvent, type HostPromptBuildResult, type HostRuntime, type HostSessionEvent, type HostTelemetryPort, type HostToolCall, type HostToolCallEvent, type HostToolResultEvent, type HostTurnEndEvent, type InstallPlan, type InstallResult, type InstallStep, type InstallTarget, type LifecycleMap, type LifecycleMapping, type MemFlywheel$1 as MemFlywheel, type MemFlywheelContext, type MemFlywheelHarnessMode, type MemFlywheelHarnessRuntime, type MemFlywheelHarnessRuntimeAdapter, type MemFlywheelHarnessRuntimeOptions, type MemFlywheelHook, type MemFlywheelLearningLoopConfig, type MemFlywheelMessage, type MemoryIndexRetrievalOptions, type OpenClawApiLike, type OpenClawHarnessPortOptions, type OpenClawNativeModelRuntime, type OpenClawNativeModelSelection, type OpenCodeClientLike, type OpenCodeHarnessPortOptions, type OpenCodeHooks, type OpenCodePluginInput, type PiAgentMessage, type PiAssistantMessage, type PiExtensionApiLike, type PiExtensionContextLike, type PiExtensionHandler, type PiImageContent, type PiModelAuthResult, type PiScribeLike, type PiSessionIdResolver, type PiStreamSimple, type PiTextContent, type PiToolCallContent, type PiToolResultMessage, type PiUserMessage, type ResolvePiAgentModel, type SkillPreludeBuilder, type SkillRecallProvider, type VerifyResult, WIRING_KEY, WIRING_VERSION, type WiringMarker, adaptSdkMemFlywheel, adapterIds, applyInstall, attachMemFlywheelToHostPort, attachPiScribe, bindLifecycle, buildPiPromptInjection, buildWiringMarker, classifyHostCapabilities, configureOpenCodeMemoryPermission, connect, createCapabilitySet, createMemFlywheelHarnessRuntime, createOpenClawHarnessPort, createOpenClawHostModel, createOpenClawPluginRuntime, createOpenCodeHarnessPort, createOpenCodeHostModel, createOpenCodePluginServer, createPiAgentModelResolver, createPiHarnessPort, defaultOpenClawMemFlywheelRoot, defaultOpenCodeMemFlywheelRoot, doctorInstall, getAdapter, hermesAdapter, hostMessagesFromOpenClawMessages, hostMessagesFromOpenCodeSessionMessages, hostMessagesFromPi, hostMessagesToMemFlywheelMessages, makeAdapter, markersEqual, memScribeMessagesFromPi, normalizeMessages, openClawHostMemoryPaths, openclawAdapter, opencodeAdapter, piAdapter, planInstall, readHostConfig, readString, readWiringMarker, registerOpenClawMemoryCapability, registerOpenClawSingleWriterGuard, requireHostCapabilities, resolveInstallTarget, createOpenCodePluginServer as server, verifyInstall, writeHostConfig };