usertrust

Pattern Memory

Per-prompt-hash memory of which model succeeded at what cost, with a suggestion API — usertrust records and suggests; it never routes.

Pattern memory records the cost and outcome of each governed LLM call by prompt hash and model, and suggestModel() returns the model that scored best for a given hash. It only suggests: usertrust never routes, and nothing in the SDK reads the suggestion or changes which model a call uses. It never stores the prompts themselves.

Privacy by Construction

Pattern memory never stores raw prompts. Every prompt is reduced to a SHA-256 hash before recording. The original text cannot be recovered from the hash. This is not a policy -- it is enforced by the API design.

Each record contains:

FieldTypeDescription
promptHashstringSHA-256 hex digest, but of what depends on the writer. govern() records sha256(JSON.stringify(promptParts)) over the provider-normalized prompt parts (messages plus system prompt; Google contents); the headless controller records sha256(transferId), a per-call id, so headless entries never repeat; hashPrompt(text) is sha256(text) of the raw string, so a hash of the raw prompt will not match a govern() entry
modelstringModel used for the call
costnumberUsertokens spent
successbooleanWhether the call succeeded
timestampstringISO 8601 timestamp

Model Suggestion

The suggestModel() function scores each model that has been used for a given prompt hash:

score = avgCost > 0 ? successRate / avgCost : successRate * 1_000_000

Higher scores indicate models that reliably succeed at lower cost. A model with a 95% success rate and an average cost of 50 usertokens scores higher than one with 98% success at 200 usertokens.

If no patterns exist for a prompt hash, suggestModel() returns null. It is synchronous and consults only the in-process cache, never memory.json directly: until recordPattern() or getPatternStats() has run in the current process, it returns null even when the file holds matching entries.

API

import {
  recordPattern,
  suggestModel,
  hashPrompt,
  getPatternStats,
} from "usertrust";

// Hash a prompt (SHA-256 of the raw string, irreversible). The SDK's own
// recorded calls are NOT keyed by hashPrompt(), so this hash only matches
// entries you record with recordPattern() yourself
const hash = hashPrompt("Summarize this document...");

// Load memory.json into this process's cache first; suggestModel() is
// synchronous and never reads the file itself
await getPatternStats();

// Get a model suggestion based on historical patterns
const suggested = suggestModel(hash);
// "claude-haiku-4-5" or null if no data exists

// Record the outcome of a call
await recordPattern({
  promptHash: hash,
  model: "claude-haiku-4-5",
  cost: 42,
  success: true,
});

// Get aggregate statistics
const stats = await getPatternStats();
// { totalEntries: number, uniqueModels: number, hitCount: Map<string, number> } — hitCount is keyed by promptHash

Storage

Patterns are stored in .usertrust/patterns/memory.json. The file is capped at 10,000 entries. When the cap is reached, the oldest entries are evicted first (FIFO).

A process-local async mutex serializes writes to prevent concurrent corruption. It offers no protection across processes: two processes sharing one vault can each read-modify-write the file and lose entries. Each write goes to a temp file and is renamed into place.

recordPattern, suggestModel, and getPatternStats each take an optional trailing vaultPath argument (recordPattern(entry, vaultPath?), suggestModel(promptHash, vaultPath?), getPatternStats(vaultPath?)) that overrides the default .usertrust directory; the in-process cache is keyed per vault path. hashPrompt(text) takes no vault argument.

Configuration

{
  "patterns": {
    "enabled": true,
    "feedProxy": false
  }
}
OptionTypeDefaultDescription
patterns.enabledbooleantrueEnable or disable pattern recording
patterns.feedProxybooleanfalseUnused. Reserved for a removed remote-proxy aggregation path; has no effect.

When patterns.enabled is false, trust() and createGovernor() skip recording entirely. The exported functions do not read the config themselves: a direct recordPattern() call still writes to memory.json, and suggestModel() still answers from whatever the process cache holds.