/** * The one body both code-facing turn drivers run. * * `turn.ts` (automations, `vendo_delegate`, `agent.chat()`, `agent.run()`, * `turns.resume()`) and `session.ts` (`agent().session()`, `agent().respond()`) * built the same runtime out of the same store doors, line for line — down to a * byte-identical comment on the render seam. This is that body, once. * * Two things it deliberately does NOT own: * * - The thread's LIFECYCLE (`ensureSchema`, `openThread`). A session opens its * thread once and streams many turns over it; `turn.ts` opens one per turn. * Owning `openThread` here would `put({ messages: [] })` on a session's second * turn and delete the conversation the caller came back to read. * - The turn's ANSWER. The wire face wants the `Response` (and stamps * `THREAD_ID_HEADER` on it); the code face drains it and reads a record back. * Both are the caller's, so this hands back the `Response` and neither pays * for the other's shape. */ import { type FilesAdapter, type Guard, type Harness, type HarnessEvent, type RunContext, type SeatModels, type Skill, type ThreadId, type ToolRegistry } from "../core/index.js"; import { type HarnessRuntimeDeps } from "../harnesses/index.js"; import { type VendoStore } from "../store/index.js"; import type { LanguageModel, UIMessage } from "ai"; import type { MemoryAdapter } from "./memory.js"; import { type SystemPromptHook } from "./prompt.js"; /** The composed world a turn runs in. Both drivers' own deps satisfy this * structurally; each resolves the two that genuinely differ (`files`, and a * `tools`/`harness` it may have wrapped) before handing it over. */ export interface SpineDeps { harness: Harness; store: VendoStore; guard: Guard; /** Where workspace blobs land. `turn.ts` falls back to the store's own rows; * a session is always composed with one. */ files: FilesAdapter; /** Projected into the read-only `/host/skills` mount. */ skills: readonly Skill[]; /** The whole tool surface for this turn — GUARD-BOUND already, by the caller. * The one choke point, so the spine never binds and never unwraps. */ tools: ToolRegistry; instructions?: string; system?: SystemPromptHook; memory?: MemoryAdapter; models?: SeatModels; liveTurn?: HarnessRuntimeDeps["liveTurn"]; /** The unattended lane's tool-call budget rides here. A session composes * none, and gaining one would cap a person's chat turn. */ bridge?: HarnessRuntimeDeps["bridge"]; } export interface SpineInput { ctx: RunContext; threadId: ThreadId; /** The one message this caller contributes. */ message: UIMessage; /** * Read the thread's prior turns back and put them before `message`. * * NOT the same question as "reopen an existing thread": a session mints its * thread once and then streams turn after turn over it, so from its SECOND * turn on there is history to read on a thread it did not reopen. Wire this * to a driver's reopen flag and a session's second turn hands the harness an * empty conversation. */ readHistory: boolean; /** * THE POSTURE. Required, never derived, never defaulted. * * `true` — a turn that asks for permission BLOCKS on the tap, and an * unanswered card dies with the turn. `false` — it returns a refusal at once * and the card STANDS, so `turns.resume()` can answer it days later. A * default here is a silent consent change. */ interactive: boolean; /** §9.7 — the org mounts the caller asserted for this turn. A session asserts * none. */ memberships?: RunContext["memberships"]; observe?: (event: HarnessEvent) => void; signal?: AbortSignal; } export declare function runHarnessTurn(deps: SpineDeps, input: SpineInput): Promise;