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:
| Field | Type | Description |
|---|---|---|
promptHash | string | SHA-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 |
model | string | Model used for the call |
cost | number | Usertokens spent |
success | boolean | Whether the call succeeded |
timestamp | string | ISO 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_000Higher 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 promptHashStorage
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
}
}| Option | Type | Default | Description |
|---|---|---|---|
patterns.enabled | boolean | true | Enable or disable pattern recording |
patterns.feedProxy | boolean | false | Unused. 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.