tom-engine
v0.1.0
Published
A deterministic, testable Theory of Mind game engine for simulating recipient minds and scoring outreach attempts
Maintainers
Readme
tom-engine
A deterministic, testable Theory of Mind game engine for simulating recipient minds and scoring outreach attempts.
Features
- Deterministic Simulation: Predictable recipient state updates based on message quality
- Quality Gates: Enforce spam-killing rules before messages reach recipients
- Mathematical Scoring: Transparent 0-100 scoring system with explainable feedback
- Level System: Configurable difficulty levels with different constraints
- No Dependencies: Pure TypeScript, no external runtime dependencies
Installation
npm install tom-engineQuick Start
import { TOMEngine, LevelConfigs } from 'tom-engine';
// Create a persona
const persona = {
role: "CFO",
values: ["efficiency", "cost reduction"],
skepticism: 0.6,
conscientiousness: 0.8,
inbox_load: 0.7,
style: "short",
taboo: ["guarantee", "revolutionary"],
};
// Create engine with level 1 config
const engine = new TOMEngine({
level: LevelConfigs.level1(),
persona,
context: {
role_pain: {
pain_point: "High operational costs",
severity: 0.8,
},
},
});
// Create a message
const message = {
content: "I noticed your company's operational costs have increased 30% this quarter. Our solution has helped similar CFOs reduce costs by 25%. Can we schedule a 15-minute call?",
evidence: [
{
text: "operational costs have increased 30% this quarter",
type: "role_pain",
},
{
text: "helped similar CFOs reduce costs by 25%",
type: "other",
},
],
ask_type: "call",
ask_clear: true,
word_count: 42,
};
// Check quality gates before sending
const gateResult = engine.checkQualityGates(message);
if (!gateResult.pass) {
console.log("Gate failures:", gateResult.failures);
console.log("Suggested fixes:", gateResult.suggested_fixes);
}
// Run the match
const result = engine.runMatch([message]);
console.log(`Outcome: ${result.outcome}`);
console.log(`Score: ${result.score}/100`);
console.log(`Final State:`, result.final_state);
console.log(`Explanation:`, result.explanation);Core Concepts
Persona
A persona represents the recipient's characteristics:
interface Persona {
role: "CFO" | "Founder" | "CTO" | ...;
values: string[]; // What they care about
skepticism: number; // 0-1, how skeptical they are
conscientiousness: number; // 0-1, attention to detail
inbox_load: number; // 0-1, how busy they are
style: "short" | "formal" | ...;
taboo: string[]; // Words/phrases that trigger irritation
}Recipient State
The recipient's internal state tracks four dimensions:
- Trust (0-1): How much they trust the sender
- Interest (0-1): How interested they are
- Confusion (0-1): How confused they are
- Irritation (0-1): How irritated they are
Outcomes
agree: Recipient agrees to the ask (best outcome)engage: Recipient asks clarifying questionsignore: Recipient ignores the messagereject: Recipient explicitly rejectsmax_turns: Maximum turns reached without agreementEND: Catastrophic failure (irritation too high or trust too low)
Quality Gates
Messages must pass quality gates before reaching the recipient:
- Brevity: Word count within limit
- No Placeholders: No
[YourName]style placeholders - Evidence Distinctness: Minimum distinct evidence texts
- Timing Evidence: Required for some levels
- Claims with Proof: Numeric claims need evidence
- Clear Ask: Must have a clear, actionable ask
Scoring
The scoring system uses:
- Turn Utility: Based on state deltas and current state
- Penalties: For quality gate violations
- Outcome Reward: Terminal reward based on outcome
- Speed Bonus: Bonus for achieving agreement quickly
Final score is a 0-100 value computed via logistic function.
API Reference
TOMEngine
Main engine class.
Constructor
new TOMEngine(config: EngineConfig)Methods
runMatch(messages: Message[]): MatchResult- Run a complete matchprocessTurn(message: Message, turnNumber: number): TurnResult- Process a single turncheckQualityGates(message: Message): GateResult- Check if message passes gatesgetRecipientState(): RecipientState- Get current recipient statereset(): void- Reset for a new matchupdateConfig(config: Partial<EngineConfig>): void- Update configurationgetConfig(): EngineConfig- Get current configuration
LevelConfigs
Predefined level configurations following the 6-level structure:
🟢 Level 1 — "CLEAR THE GATE" - Easy difficulty, friendly persona, low inbox load
- Goal: Don't get filtered out
- Win: Confusion → 0, Irritation < 0.2, Any non-negative response
- Teaches: Clear ask, Basic relevance, Length discipline
🔵 Level 2 — "MODEL THE ROLE" - Medium difficulty, role-specific skepticism
- Goal: Match incentives
- Win: Trust ≥ 0.5, Interest ≥ 0.4
- Teaches: CFO ≠ Founder ≠ Engineer, Proof requirements differ, Tone matters
🟣 Level 3 — "WHY NOW?" - Medium-Hard difficulty, timing required
- Goal: Prove timing
- Win: Recipient acknowledges relevance or timing
- Teaches: Context anchoring, Avoiding fake personalization, Moment selection
🟠 Level 4 — "MINIMAL ASK OPTIMIZATION" - Hard difficulty, high inbox load
- Goal: Earn engagement with minimal friction
- Win: Engagement with ≤ 5-minute ask, No follow-up required
- Teaches: Smallest possible ask, Respect signaling, Cognitive load awareness
🔴 Level 5 — "ADVERSARIAL MINDS" - Very Hard difficulty, one strike = END
- Goal: Survive hostile filtering
- Win: Any voluntary continuation, No END
- Teaches: Precision, Humility, Not everyone should be contacted
⚫ Level 6 — "THE 10% ROOM" - Elite difficulty, can win by SKIP
- Goal: Reach someone only you can reach
- Win: Engagement without elite intervention, Or justified SKIP with explanation
- Teaches: Comparative advantage, Self-selection, When not to play
Methods:
LevelConfigs.level1()throughlevel6()- Get specific level configLevelConfigs.get(level: number)- Get level by number (1-6)LevelConfigs.custom(overrides)- Create custom level
Examples
Multi-Turn Match
const engine = new TOMEngine({
level: LevelConfigs.level3(),
persona: { /* ... */ },
});
const messages = [
{
content: "First message...",
evidence: [/* ... */],
ask_type: "call",
ask_clear: true,
word_count: 50,
},
{
content: "Follow-up message...",
evidence: [/* ... */],
ask_type: "call",
ask_clear: true,
word_count: 45,
},
];
const result = engine.runMatch(messages);Custom Level
const customLevel = LevelConfigs.custom({
level: 2,
max_turns: 3,
max_words: 100,
weights: {
trust: 1.2,
interest: 1.0,
confusion: 1.5,
irritation: 1.8,
},
});
const engine = new TOMEngine({
level: customLevel,
persona: { /* ... */ },
});Quality Gate Pre-Check
const message = { /* ... */ };
const gateResult = engine.checkQualityGates(message);
if (!gateResult.pass) {
// Don't send, show feedback
gateResult.failures.forEach(failure => {
console.error(`${failure.type}: ${failure.message}`);
});
gateResult.suggested_fixes.forEach(fix => {
console.log(`Fix: ${fix}`);
});
} else {
// Safe to send
const result = engine.runMatch([message]);
}Mathematical Model
The scoring system implements:
- State Deltas:
ΔT_t = T_t - T_{t-1}(and similar for I, C, R) - Turn Reward:
r_t = w_T*ΔT + w_I*ΔI - w_C*ΔC - w_R*ΔR - Good-State Bonus:
b_t = β_T*T + β_I*I - β_C*C - β_R*R - Turn Score:
s_t = r_t + b_t - Penalties:
P_t = p_len + p_ph + p_ev + p_claim + p_ask - Turn Utility:
u_t = s_t - P_t - Match Utility:
U = Σu_t + O(outcome) + B_speed - Final Score:
Score = 100 * σ(k(U - μ))
See the source code for detailed implementations.
License
MIT
