/**
* Decision — Anthropic tool surface for the Rule Library.
*
* Every Pillar 3 agent exposes the same two tools to its LLM:
*
* find_rules({type, domain, agent, triggers})
* → search the library for active rules that match. Used at the
* start of any interpretation / visualization / delivery step
* (Std 5 — declared methods only).
*
* get_rule({rule_id})
* → fetch the full content of one entry. Used after the LLM
* (or the deterministic short-circuit in index.ts) picks a
* candidate so it can apply the conditions / action / confidence
* framework.
*
* Mirrors src/intelligence/tools.ts: the library is loaded lazily on
* first call and cached for the process lifetime. Failures are
* returned as structured DecisionToolResult objects so the agent's
* tool-use loop never sees a raw exception (Std 12).
*/
import {
findRules,
getRule,
type Rule,
type RuleAgent,
type RuleType,
} from './library.js';
export interface DecisionToolDescriptor {
readonly name: string;
readonly description: string;
readonly input_schema: {
readonly type: 'object';
readonly properties: Record<string, { type: string; description: string }>;
readonly required: readonly string[];
};
}
export const RULE_TOOLS: readonly DecisionToolDescriptor[] = [
{
name: 'find_rules',
description:
'Search the SME Rule Library for active entries that match the current decision-layer ' +
'operation. Filter by `type` (interpretation | visualization | delivery), `domain` ' +
'(banking | wealth | insurance | all | …), `agent` (output_ingestion | visualization | ' +
'delivery), and free-text `triggers`. Returns lightweight ' +
'{rule_id, name, type, domain, triggers, sourceFile} entries; call get_rule for the ' +
'full conditions / action / confidence_framework / disclosure_policy.',
input_schema: {
type: 'object',
properties: {
type: {
type: 'string',
description: 'Filter by rule type.',
},
domain: {
type: 'string',
description: 'Filter by domain (banking, wealth, insurance, all, …). Entries with domain="all" always match.',
},
agent: {
type: 'string',
description: 'Filter by intended Decision agent (output_ingestion, visualization, delivery).',
},
triggers: {
type: 'string',
description: 'Optional comma-separated free-text trigger terms (e.g. "peer_comparison, decision_maker"). Each term must appear in the entry\'s triggers list (case-insensitive substring).',
},
},
required: [],
},
},
{
name: 'get_rule',
description:
'Fetch the full content of one rule by its rule_id. Returns the conditions, action, ' +
'confidence_framework, disclosure_policy, and rationale.',
input_schema: {
type: 'object',
properties: {
rule_id: { type: 'string', description: 'Unique snake_case identifier.' },
},
required: ['rule_id'],
},
},
];
export interface DecisionToolResult {
readonly ok: boolean;
readonly result?: unknown;
readonly error?: { readonly category: string; readonly message: string };
}
export async function executeRuleTool(
name: string,
rawInput: unknown,
): Promise<DecisionToolResult> {
const input = (rawInput && typeof rawInput === 'object') ? (rawInput as Record<string, unknown>) : {};
try {
switch (name) {
case 'find_rules': {
const triggers = typeof input.triggers === 'string'
? input.triggers.split(',').map(s => s.trim()).filter(s => s.length > 0)
: undefined;
const results = await findRules({
type: typeof input.type === 'string' ? (input.type as RuleType) : undefined,
domain: typeof input.domain === 'string' ? input.domain : undefined,
agent: typeof input.agent === 'string' ? (input.agent as RuleAgent) : undefined,
triggers,
});
return { ok: true, result: results.map(summarize) };
}
case 'get_rule': {
const id = typeof input.rule_id === 'string' ? input.rule_id : '';
if (!id) {
return { ok: false, error: { category: 'invalid-request', message: 'rule_id is required.' } };
}
const r = await getRule(id);
if (!r) {
return { ok: false, error: { category: 'not-found', message: `no rule "${id}" in library.` } };
}
return { ok: true, result: r };
}
default:
return { ok: false, error: { category: 'unknown-tool', message: `unknown rule tool "${name}"` } };
}
} catch (err) {
return {
ok: false,
error: { category: 'internal', message: err instanceof Error ? err.message : String(err) },
};
}
}
function summarize(r: Rule) {
return {
rule_id: r.rule_id,
name: r.name,
type: r.type,
domain: r.domain,
agent: r.applies_to.agent,
triggers: r.applies_to.triggers,
status: r.status,
sourceFile: r.sourceFile,
};
}