import { AIInterface, AIMessage } from '@happyvertical/ai'; import { PrincipalAuditSink, PrincipalBinding, PrincipalTool } from '@happyvertical/smrt-agents'; import { LearningMemoryRecord, LearningSemanticSearch, SmrtClassOptions } from '@happyvertical/smrt-core'; import { AgentSession } from './models/AgentSession.js'; import { ChatMessage } from './models/ChatMessage.js'; import { ManifestTool, ToolLoopResult } from './tool-loop.js'; /** * The structural persona shape the conversation binding needs. Both a * `ResolvedPersona` (from `PersonaResolver.resolve()`) and a raw `AgentPersona` * satisfy it via the adapters below. */ export interface ConversationPersona { /** Persona id — required to scope learning memory and prompt overrides. */ id?: string | null; /** Owning tenant. */ tenantId: string | null; /** Canonical agent class the persona configures. */ agentClass?: string; /** The user whose live permissions bound the conversation. */ runAsUserId: string; /** Optional acting `Bot` profile id (identity/audit). */ actsAsProfileId?: string | null; /** The persona's tool allow-list (already capped by the class ceiling). */ allowedTools: string[]; /** Behavioural instructions / system prompt. */ instructions?: string; /** Learning memory partition key. */ memoryScope?: string; } /** Adapt a `PersonaResolver.resolve()` result into a {@link ConversationPersona}. */ export declare function conversationPersonaFromResolved(resolved: { personaId?: string; tenantId: string; agentClass: string; runAsUserId?: string; actsAsProfileId?: string | null; allowedTools: string[]; instructions: string; memoryScope: string; }): ConversationPersona; /** Adapt a raw `AgentPersona` row into a {@link ConversationPersona}. */ export declare function conversationPersonaFromAgentPersona(persona: { id?: string | null; tenantId: string; agentClass: string; runAsUserId: string; actsAsProfileId?: string | null; instructions: string; memoryScope?: string; getAllowedTools: () => string[]; }): ConversationPersona; /** * Project a {@link ConversationPersona} into the {@link PrincipalBinding} the * tool loop runs as. The persona's `allowedTools` is the fail-closed whitelist * (absent/empty ⇒ no tools). */ export declare function principalBindingFor(persona: ConversationPersona): PrincipalBinding; /** How to recall a persona's learning memory into the conversation context. */ export interface PersonaRecallOptions { /** Learning scope to recall (defaults to `'chat'`). */ scope?: string; /** Exact episode key within the scope (omit for a scope-wide recall). */ key?: string; /** Free-text query for the semantic arm (needs a `semanticSearch`). */ query?: string; /** Max recalled records injected into context. Default 5. */ limit?: number; /** Override the reuse floor for this recall. */ minConfidence?: number; /** Optional embedding search for the semantic recall arm. */ semanticSearch?: LearningSemanticSearch; } /** * Recall the persona's confidence-filtered learning memory. * * Isolated per persona by `memoryScope`, so what the "Support" persona learned * never bleeds into "Sales". Returns `[]` for a persona with no memory scope / * id (nothing to partition on). */ export declare function recallPersonaMemory(db: SmrtClassOptions['db'], persona: ConversationPersona, options?: PersonaRecallOptions): Promise; /** * Format recalled memory into a system-context block. Empty string when there * is nothing to inject (so it can be unconditionally concatenated). */ export declare function formatRecalledMemory(records: LearningMemoryRecord[]): string; /** * Resolve the persona's effective instructions. * * Prefers the prompt-system resolution (`resolvePersonaInstructions`, which * layers any approved learned-directive override) when the persona is persisted; * falls back to the inline `persona.instructions`. This is how a conversation * "uses its instructions (`applyPersonaInstructions`)". */ export declare function resolveConversationInstructions(db: SmrtClassOptions['db'], persona: ConversationPersona): Promise; /** The minimal AgentSession surface the turn needs. */ type SessionLike = Pick; /** The minimal ChatService surface the turn needs to author the reply. */ export interface ConversationReplyService { initialize(): Promise; } /** * Options for {@link runPersonaConversationTurn}. */ export interface PersonaConversationTurnOptions { /** The AI boundary. */ ai: AIInterface; /** The database handle side-door operations run against. */ db: SmrtClassOptions['db']; /** The persona the conversation is bound to. */ persona: ConversationPersona; /** The user's message this turn. */ userMessage: string; /** Tenant the turn runs within. */ tenantId: string; /** Prior conversation turns (assistant/user), oldest first. */ history?: AIMessage[]; /** * The bound agent session. When provided together with `chatService`, the * agent reply is authored into the session's room and each executed tool is * recorded as a `tool_result` message (gated by the session allow-list). */ session?: SessionLike | null; /** Chat service used to author the agent reply. */ chatService?: ConversationReplyService | null; /** Thread to attach authored messages to. */ threadId?: string | null; /** Recall configuration, or `false` to skip memory recall. */ recall?: PersonaRecallOptions | false; /** Pre-built tool catalog (else derived from the persona's `allowedTools`). */ tools?: ManifestTool[]; /** * Non-manifest tools to offer this turn — e.g. the agent-orchestration * `invoke-agent` tool (#1892). Each is filtered by the persona's * `allowedTools` before being offered, so orchestration is gated exactly like * any other tool: a persona that does not allow-list `agents.invoke` never * sees it. */ extraTools?: PrincipalTool[]; /** Max tool-executing rounds. */ maxSteps?: number; /** Model id. */ model?: string; /** Sampling temperature. */ temperature?: number; /** Max tokens per completion. */ maxTokens?: number; /** Originating user the turn runs on behalf of (audited). */ onBehalfOfUserId?: string | null; /** Audit sink for the on-behalf-of entry (forwarded to `executeAsPrincipal`). */ audit?: PrincipalAuditSink; /** Opt into Postgres RLS transaction wrapping. */ postgresRls?: boolean; /** Correlation id for the turn (feedback ties back to it). Auto-generated when omitted. */ correlationId?: string; /** * Token sink for live streaming (#1936). Forwarded to {@link runToolLoop}: the * model's text deltas stream here as they arrive. Best-effort (see * `ToolLoopOptions.onToken`); the authored assistant message remains the * authoritative final content. */ onToken?: (chunk: string) => void; } /** The outcome of a persona-bound conversation turn. */ export interface PersonaConversationTurnResult { /** The tool-loop result (final text, invocations, transcript). */ result: ToolLoopResult; /** The correlation id feedback on this turn should reference. */ correlationId: string; /** The memory recalled into the turn's context. */ recalled: LearningMemoryRecord[]; /** The system prompt assembled for the turn. */ systemPrompt: string; /** Persisted messages authored by this turn when a chat service was supplied. */ authoredMessages?: AuthoredConversationMessages; } /** Persisted assistant/tool messages emitted for a persona turn. */ export interface AuthoredConversationMessages { toolMessages: ChatMessage[]; assistantMessage: ChatMessage | null; } /** * Run one turn of a persona-bound conversation. * * Binds the conversation to the persona: recalls its learning memory, resolves * its instructions, offers only its allow-listed manifest operations, and runs * the bounded tool loop as its principal. When a `chatService` + `session` are * given the assistant reply (and each executed tool) is authored into the room, * exercising the chat layer's own fail-closed tool gate. * * @returns The loop result, the turn's correlation id, and the recalled memory. */ export declare function runPersonaConversationTurn(options: PersonaConversationTurnOptions): Promise; /** * Options for {@link bindPersonaToSession}. */ export interface BindPersonaToSessionOptions { /** Chat service exposing the owner-checked `updateAgentSessionConfig`. */ chatService: { updateAgentSessionConfig(params: { agentSessionId: string; actorProfileId: string; tenantId: string | null; allowedTools?: string[]; systemPrompt?: string; }): Promise; }; /** The session to bind. */ session: Pick; /** The session owner (the update is owner-checked, S5 #1392). */ actorProfileId: string; /** Tenant the session belongs to. */ tenantId: string | null; /** The persona to bind the session to. */ persona: ConversationPersona; /** Instructions to set as the session system prompt (else resolved). */ instructions?: string; /** Database handle used to resolve instructions when not supplied. */ db?: SmrtClassOptions['db']; } /** * Bind an {@link AgentSession} to a persona: mirror the persona's `allowedTools` * and instructions onto the session so the chat layer's own fail-closed tool * gate (S5 #1392) agrees with the loop's, and the session's system prompt speaks * the persona's voice. This is the durable side of the `chat → personas` bridge: * once bound, the session's authoring gate and the loop's offer gate share one * allow-list. */ export declare function bindPersonaToSession(options: BindPersonaToSessionOptions): Promise; export {}; //# sourceMappingURL=persona-conversation.d.ts.map