Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,99 +1,59 @@
/**
* AI Validate-Response Server Command
*
* After generating response, AI validates if it actually answers the question.
* Uses AIProviderDaemon for LLM-based evaluation.
* Thin TS shim — delegates to the Rust cognition/validate-response IPC.
* Rust owns the prompt, model call, and one-word decision parser
* (cognition/validate_response.rs). This command maps the public params
* shape into the IPC request and forwards the typed decision back.
*
* Replaces the previous parallel reimplementation (which carried its
* own prompt template + decision parser inline). Per Joel directive
* 2026-05-18 19:44Z: zero-users full-blown-Rust-dev mode — single PR
* adds the Rust path AND deletes the TS predecessor, no migration
* cadence.
*/

import { CommandBase } from '../../../../daemons/command-daemon/shared/CommandBase';
import type { JTAGContext } from '../../../../system/core/types/JTAGTypes';
import type { ICommandDaemon } from '../../../../daemons/command-daemon/shared/CommandBase';
import type { AIValidateResponseParams, AIValidateResponseResult, ResponseDecision } from '../shared/AIValidateResponseTypes';
import { AIProviderDaemon } from '../../../../daemons/ai-provider-daemon/shared/AIProviderDaemon';
import type { TextGenerationRequest } from '../../../../daemons/ai-provider-daemon/shared/AIProviderTypesV2';
import { LOCAL_MODELS } from '../../../../system/shared/Constants';
import type { AIValidateResponseParams, AIValidateResponseResult } from '../shared/AIValidateResponseTypes';
import { RustCoreIPCClient } from '../../../../workers/continuum-core/bindings/RustCoreIPC';

export class AIValidateResponseServerCommand extends CommandBase<AIValidateResponseParams, AIValidateResponseResult> {
constructor(context: JTAGContext, subpath: string, commander: ICommandDaemon) {
super('ai/validate-response', context, subpath, commander);
}

async execute(params: AIValidateResponseParams): Promise<AIValidateResponseResult> {
// Build validation prompt
const validationPrompt = this.buildValidationPrompt(params);

// Simple LLM call for validation
const request: TextGenerationRequest = {
messages: [
{ role: 'system', content: 'You are a response validator. Reply ONLY with one word: SUBMIT, CLARIFY, or SILENT.' },
{ role: 'user', content: validationPrompt }
],
model: params.model ?? LOCAL_MODELS.GATING,
temperature: 0.1, // Low temp for consistent decisions
maxTokens: 10, // Just need one word
provider: 'local'
};

const response = await AIProviderDaemon.generateText(request);

if (!response.text) {
throw new Error(response.error ?? 'AI validation failed');
}

// Parse decision
const decision = this.parseDecision(response.text);
const reason = this.getReasonForDecision(decision, params);

return {
context: params.context,
sessionId: params.sessionId,
decision,
confidence: 0.9, // High confidence for simple yes/no decisions
reason,
debug: params.verbose ? {
promptSent: validationPrompt,
aiResponse: response.text
} : undefined
};
}

private buildValidationPrompt(params: AIValidateResponseParams): string {
return `You generated this response:
"${params.generatedResponse}"

Original question from ${params.questionSender}:
"${params.originalQuestion}"

Does your response actually answer their question?

Reply with ONLY ONE WORD:
- SUBMIT (your response clearly answers the question)
- CLARIFY (you're unsure, should ask for clarification)
- SILENT (your response is off-topic, stay silent)`;
}

private parseDecision(aiResponse: string): ResponseDecision {
const text = aiResponse.trim().toUpperCase();

if (text.includes('CLARIFY')) {
return 'CLARIFY';
} else if (text.includes('SILENT')) {
return 'SILENT';
}

return 'SUBMIT'; // Default to submitting
}

private getReasonForDecision(decision: ResponseDecision, _params: AIValidateResponseParams): string {
switch (decision) {
case 'SUBMIT':
return 'Response appears relevant to the question';
case 'CLARIFY':
return 'Uncertain if response answers question, should ask for clarification';
case 'SILENT':
return 'Response is off-topic or does not address the question';
default:
return 'Unknown decision';
try {
const client = await RustCoreIPCClient.getInstanceAsync();
const decision = await client.cognitionValidateResponseDecision({
generatedResponse: params.generatedResponse,
originalQuestion: params.originalQuestion,
questionSender: params.questionSender,
model: params.model,
});

return {
context: params.context,
sessionId: params.sessionId,
decision: decision.decision,
confidence: decision.confidence,
reason: decision.reason,
debug: params.verbose ? {
promptSent: '(Rust-owned — see cognition::validate_response logs)',
aiResponse: '(Rust-owned — see cognition::validate_response logs)',
} : undefined,
};
} catch (error) {
return {
context: params.context,
sessionId: params.sessionId,
error: error instanceof Error ? error.message : String(error),
decision: 'SUBMIT', // Fail-open: ship the draft when validator fails
confidence: 0.0,
reason: `Validation error: ${error instanceof Error ? error.message : String(error)}`,
};
}
}
}
2 changes: 1 addition & 1 deletion src/eslint-baseline.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
5433
5432
7 changes: 7 additions & 0 deletions src/shared/generated/cognition/ResponseDecision.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.

/**
* Three-way decision: SUBMIT (post the draft), CLARIFY (ask follow-up),
* SILENT (drop the draft). Mirrors TS `ResponseDecision`.
*/
export type ResponseDecision = "SUBMIT" | "CLARIFY" | "SILENT";
7 changes: 7 additions & 0 deletions src/shared/generated/cognition/ValidateResponseDecision.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ResponseDecision } from "./ResponseDecision";

/**
* IPC response: the validation decision + provenance.
*/
export type ValidateResponseDecision = { decision: ResponseDecision, confidence: number, reason: string, model: string, timestamp: number, };
7 changes: 7 additions & 0 deletions src/shared/generated/cognition/ValidateResponseRequest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.

/**
* IPC request: ask cognition whether a draft response actually answers
* the original question.
*/
export type ValidateResponseRequest = { generatedResponse: string, originalQuestion: string, questionSender: string, model?: string, };
3 changes: 3 additions & 0 deletions src/shared/generated/cognition/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export type { ResolvedModel } from './ResolvedModel';
export type { ResourceAdmissionPolicy } from './ResourceAdmissionPolicy';
export type { ResourceClass } from './ResourceClass';
export type { ResponderDecision } from './ResponderDecision';
export type { ResponseDecision } from './ResponseDecision';
export type { ResponseProposal } from './ResponseProposal';
export type { SemanticSearchResult } from './SemanticSearchResult';
export type { SemanticSearchToolsRequest } from './SemanticSearchToolsRequest';
Expand Down Expand Up @@ -89,6 +90,8 @@ export type { ToolError } from './ToolError';
export type { ToolExecutionContext } from './ToolExecutionContext';
export type { ToolInvocation } from './ToolInvocation';
export type { ToolOutcome } from './ToolOutcome';
export type { ValidateResponseDecision } from './ValidateResponseDecision';
export type { ValidateResponseRequest } from './ValidateResponseRequest';
export type { VisionDescribeOptions } from './VisionDescribeOptions';
export type { VisionDescribeRequest } from './VisionDescribeRequest';
export type { VisionDescription } from './VisionDescription';
25 changes: 25 additions & 0 deletions src/workers/continuum-core/bindings/modules/cognition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ import type {
EmbedToolsResponse,
SemanticSearchToolsRequest,
SemanticSearchResult,
ValidateResponseRequest,
ValidateResponseDecision,
} from '../../../../shared/generated';
import type { PersonaResponse } from '../../../../shared/generated/cognition/PersonaResponse';
import type { RecipeTurnBatchPlan } from '../../../../shared/generated/cognition/RecipeTurnBatchPlan';
Expand Down Expand Up @@ -137,6 +139,7 @@ export interface CognitionMixin {
cognitionGenerateResponse(params: GenerateResponseRequest): Promise<GenerateResponseResult>;
cognitionEmbedTools(params: EmbedToolsRequest): Promise<EmbedToolsResponse>;
cognitionSemanticSearchTools(params: SemanticSearchToolsRequest): Promise<SemanticSearchResult[]>;
cognitionValidateResponseDecision(params: ValidateResponseRequest): Promise<ValidateResponseDecision>;

/**
* Run the per-persona admission gate over a single InboxMessage.
Expand Down Expand Up @@ -974,6 +977,28 @@ export function CognitionMixin<T extends new (...args: any[]) => RustCoreIPCClie
return response.result as SemanticSearchResult[];
}

/**
* Rust-owned response validation. TypeScript keeps no validation
* logic; Rust owns prompt assembly, Groq call, single-word
* decision parser (SUBMIT/CLARIFY/SILENT). Replaces the legacy
* TS-side AIValidateResponseServerCommand reimpl.
*/
async cognitionValidateResponseDecision(params: ValidateResponseRequest): Promise<ValidateResponseDecision> {
const response = await this.request({
command: 'cognition/validate-response-decision',
generatedResponse: params.generatedResponse,
originalQuestion: params.originalQuestion,
questionSender: params.questionSender,
model: params.model,
});

if (!response.success) {
throw new Error(response.error ?? 'Failed to validate response');
}

return response.result as ValidateResponseDecision;
}

/**
* Per-persona response cycle (shared cognition pipeline).
* Single IPC call → Rust does analysis (cached) + scoring + prompt
Expand Down
1 change: 1 addition & 0 deletions src/workers/continuum-core/src/cognition/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ pub mod throughput_lease;
pub mod tool_embedding;
pub mod tool_executor;
pub mod turn_batch;
pub mod validate_response;
pub mod types;
pub mod vision_describe;

Expand Down
Loading
Loading