feat(policy): enforce truthfulness and autonomy guardrails

Add runtime truthfulness modes and autonomy-level tool gating with audit metadata for overrides/denials.

Wire policy through prompt assembly, tool execution context, and daemon/gateway agent paths; update tests and planning state for Phase 3 PR #2 completion.
This commit is contained in:
William Valentin
2026-02-12 16:06:45 -08:00
parent 125af4e832
commit 90ce622080
18 changed files with 1172 additions and 104 deletions
+42 -4
View File
@@ -1,5 +1,7 @@
import { readFileSync, existsSync } from 'fs';
import { resolve } from 'path';
import type { ContextLevel, TruthfulnessMode } from '../config/schema.js';
import { getTruthfulnessGuidance } from '../backends/native/guardrails.js';
/** Ordered list of prompt template files to look for. */
const PROMPT_FILES = [
@@ -15,6 +17,10 @@ export interface PromptTemplateConfig {
searchDirs: string[];
/** Additional sections to inject (e.g., from config). */
extraSections?: Array<{ name: string; content: string }>;
/** Prompt context depth. Defaults to normal. */
contextLevel?: ContextLevel;
/** Truthfulness enforcement mode. Defaults to standard. */
truthfulnessMode?: TruthfulnessMode;
}
export interface PromptTemplateResult {
@@ -32,10 +38,21 @@ export interface PromptTemplateResult {
* Sections are assembled in the order defined in PROMPT_FILES.
*/
export function assembleSystemPrompt(config: PromptTemplateConfig): PromptTemplateResult {
const level = config.contextLevel ?? 'normal';
const truthfulnessMode = config.truthfulnessMode ?? 'standard';
const includeAllTemplates = level !== 'minimal';
const includeExtraSections = level !== 'minimal';
const includeDebugSection = level === 'debug';
const includeTruthfulness = truthfulnessMode === 'strict' || truthfulnessMode === 'standard';
const sections: string[] = [];
const loadedFiles: string[] = [];
for (const { name, section } of PROMPT_FILES) {
if (!includeAllTemplates && name !== 'SOUL.md') {
continue;
}
for (const dir of config.searchDirs) {
const filePath = resolve(dir, name);
if (existsSync(filePath)) {
@@ -55,7 +72,7 @@ export function assembleSystemPrompt(config: PromptTemplateConfig): PromptTempla
}
// Add extra sections
if (config.extraSections) {
if (includeExtraSections && config.extraSections) {
for (const { name, content } of config.extraSections) {
if (content.trim()) {
sections.push(`# ${name}\n\n${content.trim()}`);
@@ -63,6 +80,11 @@ export function assembleSystemPrompt(config: PromptTemplateConfig): PromptTempla
}
}
// Inject truthfulness guidance (for strict and standard modes)
if (includeTruthfulness) {
sections.push(getTruthfulnessGuidance(truthfulnessMode));
}
// Inject current date/time as runtime context
const now = new Date();
const dateStr = now.toLocaleDateString('en-US', {
@@ -80,10 +102,26 @@ export function assembleSystemPrompt(config: PromptTemplateConfig): PromptTempla
const runtimeContext = `# Runtime Context\n\nCurrent date: ${dateStr}\nCurrent time: ${timeStr}`;
sections.push(runtimeContext);
// Fallback if only the runtime context was loaded (no actual prompt files)
if (sections.length === 1) {
if (includeDebugSection) {
const loadedFilesList = loadedFiles.length > 0
? loadedFiles.map((filePath) => `- ${filePath}`).join('\n')
: '- none';
const searchDirsList = config.searchDirs.length > 0
? config.searchDirs.map((dir) => `- ${dir}`).join('\n')
: '- none';
sections.push(
`# Prompt Debug\n\nContext level: ${level}\n\nLoaded files:\n${loadedFilesList}\n\nDirectory resolution notes:\n${searchDirsList}\n- First match wins per template file.`,
);
}
// Fallback when no prompt template files were found.
if (loadedFiles.length === 0) {
const truthfulnessSection = includeTruthfulness
? `${getTruthfulnessGuidance(truthfulnessMode)}\n\n`
: '';
return {
prompt: `You are Flynn, a helpful personal AI assistant. Be direct, concise, and helpful. Use markdown when it improves readability.\n\n${runtimeContext}`,
prompt: `You are Flynn, a helpful personal AI assistant. Be direct, concise, and helpful. Use markdown when it improves readability.\n\n${truthfulnessSection}${runtimeContext}`,
loadedFiles: [],
};
}