/** * AttendantInstance — the per-agent memory orchestrator for iranti. * * One instance lives per `agentId` per process (managed by `registry.ts`). * It is the class that implements the handshake/attend/observe/reconvene * protocol the Claude Code hooks drive. Every MCP tool call from an agent * routes through an `AttendantInstance`. * * Core operations: * - `handshake(input)` — session start: load operating rules, build a * working-memory brief, track compliance state, detect first-party mode. * - `attend(input)` — per-turn memory gate. Three phases: * • pre-response — initial retrieval + injection before the agent replies * • mid-turn — sub-turn loop (B8 M6) for retrieval retries on partial * responses when novel entity hints appear in the partial * • post-response — closeout: extract tool-result autowrites (M2/M3/M7), * personal memory corrections, checkpoint continuity keys, * session ledger learning injection, compliance scoring * - `observe(input)` — retrieval-only, no protocol tracking; used by the * MCP `iranti_observe` tool for ad-hoc context loading * - `reconvene(input)` — post-compaction re-handshake; re-delivers operating * rules and rebuilds the brief from the current knowledge state * * Memory scoring and injection: * - Hybrid search (lexical + vector) via `hybridSearch` / `searchWithBridging` * - Semantic fact tags via `buildSemanticFactTags` — topic-based relevance signals * - Confidence blending with source-reliability weights * - Compact-mode injection for high-confidence, recently-accessed facts * * Session compliance: * - Tracks LLM call count, write count, persist-warning thresholds * - Emits staff events for every attend, write, compliance gate * - Protocol enforcement via `AgentProtocolTracker` (B6 A-series gates) * * Council + sub-turn loop integration: * - At post-response, calls `planCouncilConsultation` to propose cross-staff * consultations based on low-confidence injection surfaces * - At mid-turn, calls `planSubTurnLoop` to decide whether to re-run retrieval * with novel tokens extracted from the partial response */ import { type SessionLedgerLearning } from '../lib/sessionLedger'; import { type CouncilConsultationPlan } from '../staff/council'; import { type SubTurnLoopPlan } from '../staff/subTurnLoop'; export declare const DEFAULT_ATTENDANT_OPERATING_RULES: string[]; export interface AgentContext { task: string; recentMessages: string[]; postCompaction?: boolean; ledgerContext?: { source?: string; host?: string | null; }; } export interface WorkingMemoryEntry { entityKey: string; summary: string; confidence: number; source: string; lastUpdated: string; } export interface ProjectPolicyEntry { entityKey: string; summary: string; key: string; source: string; lastUpdated: string; rules: string[]; } export type SessionObjectiveSource = 'task_string' | 'checkpoint_continuation' | 'skipped'; export interface SessionObjective { primary: string; taskSignals: string[]; confidence: number; source: SessionObjectiveSource; derivedAt: string; note: string; } export interface WorkingMemoryBrief { agentId: string; operatingRules: string; inferredTaskType: string; sessionObjective?: SessionObjective | null; workingMemory: WorkingMemoryEntry[]; projectPolicies?: ProjectPolicyEntry[]; sessionStarted: string; briefGeneratedAt: string; contextCallCount: number; backfillSuggestion?: BackfillSuggestion | null; planProposal?: PlanProposal | null; sessionLedgerLearnings?: SessionLedgerLearning[]; sessionCheckpoint?: SessionCheckpointRecord | null; sessionRecovery?: SessionRecoveryInfo | null; compliance?: SessionComplianceState | null; watchedEntities?: string[]; pendingMemoryAttributions?: MemoryAttributionResult[]; } export interface BackfillSuggestion { suggested: boolean; reason: string; candidateFacts: number; sampleKeys: string[]; suggestedCommand: string; } export type PlanProposalSource = 'heuristic_keyword' | 'checkpoint_continuation' | 'skipped'; export interface PlanProposalStep { n: number; description: string; rationale?: string; } export interface PlanProposal { suggested: boolean; reason: string; source: PlanProposalSource; steps: PlanProposalStep[]; taskSignals: string[]; note?: string; } export type SessionStatus = 'active' | 'interrupted' | 'completed' | 'abandoned'; export type SessionComplianceStatus = 'healthy' | 'degraded' | 'non_compliant'; export type SessionComplianceIssueCode = 'missing_post_response_attend' | 'missing_durable_persistence' | 'missing_writes_across_turns' | 'ignored_injected_memory'; export interface SessionComplianceIssue { code: SessionComplianceIssueCode; severity: 'warn' | 'error'; count: number; message: string; requiredAction: string; } export interface SessionComplianceState { status: SessionComplianceStatus; summary: string; issues: SessionComplianceIssue[]; lastUpdated: string; counters: { attendsWithoutPersist: number; turnsWithoutWrite: number; midTurnAttendsThisTurn: number; consecutivePreResponseWithoutPost: number; consecutiveUnusedMemoryInjections: number; pendingPostResponse: boolean; lastAttendPhase: 'pre-response' | 'post-response' | 'mid-turn' | null; }; } export interface SessionCheckpointPayload { currentStep?: string; nextStep?: string; openRisks?: string[]; recentOutputs?: string[]; actions?: Array<{ kind: string; summary: string; status?: string; target?: string; detail?: string; }>; fileChanges?: Array<{ action: string; path: string; toPath?: string; purpose?: string; }>; entityTargets?: string[]; notes?: string; } export interface SessionCheckpointRecord { sessionId: string; task: string; taskFingerprint: string; status: SessionStatus; startedAt: string; lastHeartbeatAt: string; updatedAt: string; checkpoint: SessionCheckpointPayload; interruptedAt?: string; completedAt?: string; abandonedAt?: string; resumedAt?: string; } export interface SessionRecoveryInfo { available: boolean; sessionId: string; task: string; taskFingerprint: string; matchedCurrentTask: boolean; matchConfidence: number; recommendation: 'resume' | 'review' | 'ignore'; summary: string; lastHeartbeatAt: string; interruptedAt: string; checkpoint: SessionCheckpointPayload | null; } export interface PersistedSessionState { agentId: string; sessionStarted: string; briefGeneratedAt: string; sessionCheckpoint: SessionCheckpointRecord | null; sessionRecovery: SessionRecoveryInfo | null; compliance?: SessionComplianceState | null; pendingMemoryAttributions?: MemoryAttributionResult[]; } export interface SessionInspection { agentId: string; hasCheckpoint: boolean; sessionCheckpoint: SessionCheckpointRecord | null; sessionRecovery: SessionRecoveryInfo | null; compliance: SessionComplianceState; persistedBriefGeneratedAt?: string; summary: SessionSummary; } export type SessionOperatorState = 'none' | SessionStatus; export interface SessionCheckpointSummary { currentStep: string | null; nextStep: string | null; openRiskCount: number; entityTargetCount: number; actionCount: number; } export interface SessionSummary { agentId: string; hasCheckpoint: boolean; sessionId: string | null; task: string | null; status: SessionStatus | null; operatorState: SessionOperatorState; startedAt: string | null; lastHeartbeatAt: string | null; updatedAt: string | null; interruptedAt: string | null; completedAt: string | null; abandonedAt: string | null; resumedAt: string | null; isStale: boolean; persistedBriefGeneratedAt?: string; checkpointSummary: SessionCheckpointSummary | null; compliance: SessionComplianceState | null; } export interface ObserveInput { currentContext: string; maxFacts?: number; entityHints?: string[]; priorityKeys?: string[]; skipContextFilter?: boolean; recoveryKeys?: string[]; semanticFilter?: import('../lib/semanticFactTags').SemanticFilter; ledgerContext?: AgentContext['ledgerContext']; } export interface FactInjection { factId?: string; knowledgeEntryId?: number; entityKey: string; summary: string; value: unknown; confidence: number; source: string; lastUpdated?: string; } export interface ObserveResult { facts: FactInjection[]; entitiesDetected: string[]; alreadyPresent: number; totalFound: number; usageGuidance: { tool: 'observe' | 'attend'; reminder: string; expectedCallSequence?: string[]; note: string; }; entitiesResolved?: Array<{ name: string; input: string; canonicalEntity: string; confidence: number; matchedBy: 'exact' | 'alias' | 'created' | 'hint'; }>; debug?: { skipped?: 'empty_context'; contextLength: number; detectionWindowChars: number; detectedCandidates: number; keptCandidates: number; hintsProvided?: number; hintsResolved?: number; dropped: Array<{ name: string; reason: string; }>; midTurnFilteredKeys?: string[]; }; } export type PendingToolCallName = 'Read' | 'Grep' | 'Glob' | 'Bash' | 'WebSearch' | 'WebFetch'; export interface PendingToolCall { name: PendingToolCallName; args?: Record; } export interface ToolResultPayload { toolName: PendingToolCallName; status: 'success' | 'error'; content: string; metadata?: { path?: string; url?: string; query?: string; command?: string; durationMs?: number; }; } export interface AttendInput extends ObserveInput { latestMessage?: string; forceInject?: boolean; suppressEvents?: boolean; phase?: 'pre-response' | 'post-response' | 'mid-turn'; pendingToolCall?: PendingToolCall; toolResult?: ToolResultPayload; partialResponse?: string; findings?: string; } export interface SessionCheckpointInput extends AgentContext { sessionId?: string; heartbeatAt?: string; checkpoint: SessionCheckpointPayload | string | Record; } export interface SessionActionInput { sessionId?: string; ledgerContext?: AgentContext['ledgerContext']; } export interface AttendDecision { needed: boolean; confidence: number; method: 'heuristic' | 'llm' | 'forced' | 'advisory'; explanation: string; } export interface AttendSearchSuggestion { hint: string; suggestedTerms: string[]; alternativeEntities: string[]; } export interface AttendResult extends ObserveResult { shouldInject: boolean; reason: 'forced' | 'memory_not_needed' | 'memory_needed_no_facts' | 'memory_checked_no_match' | 'memory_needed_but_in_context' | 'memory_needed_injected'; decision: AttendDecision; bootstrap?: AttendBootstrapInfo | null; searchSuggestion?: AttendSearchSuggestion; complianceWarning?: string; compliance: SessionComplianceState; memoryAttributions?: MemoryAttributionResult[]; memorySearchPerformed?: boolean; memoryResultsConsidered?: number; postResponseCapture?: PostResponseCaptureInfo; capture?: BiDirectionalCaptureResult; matchedUserRules?: MatchedUserRule[]; toolCallGuidance?: ToolCallGuidance; toolResultExtraction?: ToolResultExtractionOutcome; writeNudge?: WriteNudge; drift?: DriftSignal; autoCheckpointSignal?: AutoCheckpointSignal; refinementPass?: RefinementPass; attendantToolPlan?: AttendantToolPlan; councilConsultationPlan?: CouncilConsultationPlan; subTurnLoopPlan?: SubTurnLoopPlan; responseFileCapture?: ResponseFileCaptureResult; } export interface ToolCallGuidance { toolName: PendingToolCallName; derivedEntities: string[]; factCount: number; note: string; shouldSkip: boolean; skipConfidence: number; skipReasonCode: 'facts_cover_target' | 'no_facts_for_target' | 'no_entities_derived' | 'memory_not_needed'; } export interface DriftSignal { detected: boolean; overlap: number; drivingTokens: string[]; missingTokens: string[]; declaredTaskType: string; note: string; } export type AutoCheckpointReason = 'tool_cost_threshold' | 'turns_without_write' | 'drift_detected' | 'none'; export type AutoCheckpointUrgency = 'suggested' | 'strong' | 'critical'; export interface AutoCheckpointSignal { reason: AutoCheckpointReason; urgency: AutoCheckpointUrgency; toolCallsSinceLastWrite: number; turnsWithoutWrite: number; driftOverlap: number | null; draftCurrentStep: string | null; draftNextStep: string | null; message: string; } export type RefinementPassOutcome = 'not_needed' | 'attempted_added' | 'attempted_empty' | 'declined_no_hints' | 'declined_post_response'; export interface RefinementPass { outcome: RefinementPassOutcome; attempted: boolean; initialFactCount: number; addedFactCount: number; widenedEntityHints: string[]; fallbackTerms: string[]; reason: string; note: string; } export type AttendantProposedToolName = 'search_related' | 'observe_entity' | 'query'; export interface AttendantProposedToolCall { name: AttendantProposedToolName; args: Record; reason: string; confidence: number; } export interface AttendantToolPlan { proposed: AttendantProposedToolCall[]; basis: 'brief_entities' | 'drift_tokens' | 'objective_signals' | 'empty'; note: string; } export interface ToolResultExtractionOutcome { toolName: PendingToolCallName; autowriteBatchId: string; factsExtracted: number; factsWritten: number; skipped: Array<{ reason: string; detail?: string; }>; writtenEntries: Array<{ entity: string; key: string; summary: string; }>; targetEntity: string; durationMs: number; extractorError?: string; } export interface WriteNudge { reason: 'tool_cost_threshold'; toolCallsSinceLastWrite: number; threshold: number; draftFacts: Array<{ entity: string; key: string; summary: string; fromTool: PendingToolCallName; autowriteBatchId: string; }>; message: string; } export interface PostResponseCaptureInfo { factsExtracted: number; factsWritten: number; checkpointExtracted: boolean; skipped: Array<{ key: string; reason: string; }>; } export interface BiDirectionalCaptureResult { factsExtracted: number; factsWritten: number; skippedDupe: number; skippedRepeat: number; skippedBoilerplate: number; observedBatchId: string; sourceLabel: string; phase: string; findingsRequired?: boolean; skipped: Array<{ reason: string; detail?: string; }>; } export interface ResponseFileCaptureResult { autowriteBatchId: string; filesDetected: number; factsWritten: number; entities: string[]; skipped: Array<{ reason: string; detail?: string; }>; durationMs: number; } export type MemoryAttributionEvidenceKind = 'write' | 'checkpoint' | 'rediscovery' | 'response_reference' | 'response_recovery' | 'task_irrelevant'; export interface MemoryAttributionResult { injectionId: string; surfaced: boolean; used: boolean; helpful: boolean; status: 'pending' | 'scored'; phase: 'pre-response' | 'mid-turn'; surfacedAt: string; scoredAt?: string; reason: string; injectedKeys: string[]; injectedEntryIds: number[]; injectedSummaries?: string[]; evidenceKinds: MemoryAttributionEvidenceKind[]; taskContext?: string; } export interface AttendBootstrapInfo { handshakePerformed: boolean; reason: 'no_existing_brief'; task: string; operatingRules?: string; note?: string; } export declare function normalizeExplicitTask(task: string | null | undefined): string | null; export declare function formatOperatingRulesText(rawValue: unknown, summary?: string | null, fallbackRules?: string[]): string; export interface MatchedUserRule { entityKey: string; key: string; rule: string; triggers: string[]; scope: string; enforcement: 'soft' | 'hard'; source: string; lastUpdated: string; } export declare function extractRuleTriggers(properties: Record | null | undefined): string[]; export declare function matchesRuleTriggers(triggers: string[], contextTokens: Set, contextLower: string): boolean; export declare function formatMatchedUserRules(rules: MatchedUserRule[]): string; export declare function extractFilePathEntityHints(text: string, projectEntity: string | null): string[]; export declare function detectFileAction(contextWindow: string): 'edited' | 'created' | 'read'; /** * Derive entity hints from a pending tool call. Pure, stateless, safe to call * from tests. Callers should merge the result into `effectiveEntityHints` * *after* text-derived hints so text signals still take precedence when a hint * appears in both places. */ export declare function resolveAutowriteTargetEntity(toolResult: ToolResultPayload, projectEntity: string | null): string; export declare function parseAutowriteEntity(entity: string): { entityType: string; entityId: string; }; export declare function buildToolResultContextLabel(toolResult: ToolResultPayload): string; export declare function normalizeAutowriteFact(raw: unknown): { key: string; value: unknown; summary: string; confidence: number; } | null; export declare function derivePendingToolCallEntityHints(toolCall: PendingToolCall | undefined, projectEntity: string | null): string[]; export type PlanSignal = 'ship_release' | 'implement_add' | 'refactor_rewrite' | 'fix_debug' | 'audit_review' | 'migrate' | 'test_author' | 'deploy'; interface PlanSignalMatch { signal: PlanSignal; matchedKeyword: string; } export declare function detectPlanSignals(task: string): PlanSignalMatch[]; export declare function buildPlanProposal(task: string, inferredTaskType: string, sessionCheckpoint?: SessionCheckpointRecord | null): PlanProposal; export declare function deriveSessionObjective(options: { task: string; recentMessages?: readonly string[]; checkpointNextStep?: string | null; now?: string; }): SessionObjective; export interface SkipVerdict { shouldSkip: boolean; skipConfidence: number; skipReasonCode: ToolCallGuidance['skipReasonCode']; } export declare function deriveSkipVerdict(options: { derivedEntities: readonly string[]; factConfidences: readonly number[]; memoryWasSkipped: boolean; }): SkipVerdict; export declare function detectTaskDrift(currentMessage: string, currentContext: string, declaredTaskType: string): DriftSignal | undefined; export declare const AUTO_CHECKPOINT_TOOL_COST_THRESHOLD = 15; export declare const AUTO_CHECKPOINT_TURN_THRESHOLD = 3; export declare function detectAutoCheckpointTrigger(options: { toolCallsSinceLastWrite: number; turnsWithoutWrite: number; driftDetected: boolean; driftOverlap?: number | null; sessionObjective?: SessionObjective | null; driftDrivingTokens?: readonly string[]; }): AutoCheckpointSignal | undefined; export declare function planRefinementPass(options: { initialFactCount: number; decisionNeeded: boolean; phase?: 'pre-response' | 'mid-turn' | 'post-response'; entityHints: readonly string[]; latestMessage: string; }): RefinementPass; export declare function planAttendantToolCalls(options: { phase?: 'pre-response' | 'mid-turn' | 'post-response'; entityHints: readonly string[]; initialFactCount: number; sessionObjective?: SessionObjective | null; drift?: DriftSignal | undefined; briefHasEntities: boolean; }): AttendantToolPlan; export declare function readPersistedSessionState(agentId: string): Promise; export declare function summarizeSessionState(agentId: string, checkpoint: SessionCheckpointRecord | null, persistedBriefGeneratedAt?: string, compliance?: SessionComplianceState | null): SessionSummary; /** * Determine whether injected facts are relevant to the current task context. * Uses token overlap between the task description and fact keys/summaries. * Exported for unit testing. */ export declare function injectedFactsAreTaskRelevant(taskContext: string | undefined, injectedKeys: string[], injectedSummaries: string[] | undefined): boolean; export declare class AttendantInstance { private agentId; private brief; private advisoryLearningProfile; private contextCallCount; private attendsWithoutPersist; private turnsWithoutWrite; private midTurnAttendsThisTurn; private writeOccurredThisTurn; private consecutivePreResponseWithoutPost; private consecutiveUnusedMemoryInjections; private lastAttendPhase; private complianceUpdatedAt; private sessionStarted; private sessionCheckpoint; private eventSource; private eventHost; private sharedStateObservedAt; private pendingSharedStateInvalidations; private pendingMemoryAttributions; private rulesDelivered; private postCompactionPending; private toolCallsSinceLastWrite; private recentAutowriteBatches; private observedSummaryCache; private writeNudgeEmittedThisTurn; constructor(agentId: string); setLedgerContext(context?: AgentContext['ledgerContext']): void; private buildEventMetadata; private updateBriefPendingMemoryAttributions; private addPendingMemoryAttribution; private recordMemoryEvidence; private responseMentionsInjectedMemory; private responseShowsRecoveryValue; private checkInjectedFactsTaskRelevant; private scorePendingMemoryAttributions; noteDiscoveryOccurred(): Promise; private loadSessionLedgerSignals; private loadProjectPolicies; handshake(context: AgentContext): Promise; reconvene(context: AgentContext): Promise; updateWorkingMemory(entry: WorkingMemoryEntry): void; onContextLow(): Promise; getBrief(): WorkingMemoryBrief | null; private verifyCheckpointAvailability; private buildComplianceState; /** True when every compliance counter is zero — agent is fully on protocol. */ private get isComplianceHealthy(); private runToolResultAutowrite; private runResponseFileCapture; private runObservedCapture; private maybeBuildWriteNudge; notifyWriteOccurred(): Promise; checkpoint(input: SessionCheckpointInput): Promise; resumeSession(input?: SessionActionInput): Promise; completeSession(input?: SessionActionInput): Promise; abandonSession(input?: SessionActionInput): Promise; inspectSession(context?: Partial): Promise; getAgentId(): string; attend(input: AttendInput): Promise; observe(input: ObserveInput): Promise; private loadMatchingUserRules; private buildRecovery; private ensureSessionLoaded; private decideMemoryNeed; private buildParseFailureFallbackDecision; private buildAdvisoryMemoryDecision; private resolveAttendEntityHints; private resolveObserveEntityHints; private detectRelevantFreshState; private expandRelevantFreshTargets; private resolveFreshEntityTarget; private updateWatchedEntities; isWatchingEntity(entity: string): boolean; notifySharedEntityUpdated(entity: string, key: string): void; private consumePendingFreshState; private markSharedStateObserved; private parseMemoryDecision; private inferTask; private loadOperatingRules; private buildWorkingMemory; private persistState; private loadPersistedState; } export {}; //# sourceMappingURL=AttendantInstance.d.ts.map