Eliza
A pattern-matching conversational agent built on a rule-based engine with persona support, session persistence, and both CLI and web interfaces. Inspired by the original ELIZA program. ESM-only TypeScript project running on Node.js + Express.
Overview
Eliza implements classic ELIZA-style pattern matching with modern enhancements: weighted rule selection, topic memory, response variety tracking, and a full persona system with 16 distinct conversational personas. The project shipped through 22 development phases across 4 milestones (v1.0–v1.4), with 350 tests and 91% coverage at completion.
The engine is cleanly separated from the web layer — it can run headless via CLI, serve a REST API via Express, or be imported as a library via browser.ts exports. A StorageProvider interface abstracts persistence, with a FileStorage implementation using atomic writes and a write queue to prevent data corruption.
Architecture
src/
engine/ — Core: pattern matching, context tracking, topic memory, response variety
personas/ — 16 persona definitions (rules, greetings, state transitions)
middleware/ — Express middleware (session, CORS, security, validation)
cli/ — Interactive CLI with readline-based REPL
server.ts — Express web server (API + static frontend)
browser.ts — Browser-compatible engine exports
index.ts — Library entry point
persistence/ — StorageProvider interface + FileStorage implementation
rules/ — JSON rule loader with AJV schema validation
Key modules:
| Module | Purpose |
|---|---|
engine/matcher.ts | Pattern matching with capture groups and $N substitution |
engine/topics.ts | TopicMemory — tracks user topics with configurable return gaps |
engine/variety.ts | ResponseHistory — cycles through responses to prevent repetition |
engine/context.ts | ContextTracker — conversation context window across turns |
engine/responseSelector.ts | ResponseSelector — style-driven, recency-weighted response selection |
persistence/fileStorage.ts | Atomic file writes with proper-lockfile and structured error logging via winston |
Tech Stack
| Technology | Version | Purpose |
|---|---|---|
| TypeScript | ^5.6.0 | Primary language — strict mode, ES2022 target |
| Node.js | >=18.0.0 | Runtime — ESM modules (NodeNext) |
| Express.js | ^5.2.1 | Web API and static frontend serving |
| Vitest | ^4.0.17 | Test framework — V8 coverage |
| helmet | ^8.0.0 | Security headers (CSP, frameguard, referrer-policy) |
| ajv | ^8.18.0 | JSON schema validation for rule files |
| proper-lockfile | ^4.1.2 | Atomic file locking for persistence |
| winston | ^3.19.0 | Structured error logging |
Persona System
16 personas defined as JSON files in src/personas/definitions/: classic, supportive, assistant, career, challenger, developer, dietitian, lifecoach, mindfulness, motivational, philosopher, productivity, quiz, stu, trainer, tutor.
Each persona defines:
rules— pattern-response pairs with optionalstatetransitionsgreetings— conversation openersstateTransitions— topic flow definitionsfallbacks— emotion-specific and generic fallback responses
Rule precedence: persona-specific rules override defaults; higher-weight rules take precedence over lower-weight rules; ties use stable first-match-wins sort.
Rule System
14 JSON rule categories: emotions, family, work, dreams, health, self, greeting, goodbye, and others. Rules are loaded and validated against an AJV schema at startup. Pattern matching uses regex with capture groups; $N substitution assembles responses from captured text with safe reflection (first-person pronouns are not reflected back).
Weighted rule matching (introduced v1.3): phrase patterns default to weight 10, keyword patterns to weight 0, enabling clear priority layers without breaking existing rules.
Key Decisions
| Decision | Rationale | Outcome |
|---|---|---|
| CLI first, web second | Faster iteration on core logic before UI complexity | Good |
| Pattern matching over AI | Learning project focused on rule-based systems | Good |
| ESM modules over CommonJS | Cleaner imports, modern JavaScript standards | Good |
| TypeScript strict mode | Type safety, better IDE tooling, fewer runtime errors | Good |
| First-match-wins rules | Simple, predictable pattern matching behavior | Good |
TopicMemory class pattern | Consistent state tracking across conversation | Good |
ResponseHistory class pattern | Prevents repetitive responses, cycles through all options | Good |
| Express.js for web API | Minimal setup, familiar patterns, good TypeScript support | Good |
| Default weight 0 for rules | Backward compatibility with existing rules | Good |
| 3+ turn gap for topic return | Avoids “you just mentioned” for immediate repeats | Good |
| Avoid first-person reflection | Prevents subject confusion in captured groups with “I” | Good |
Development Status
- Version: v1.4 (complete) — all 22 phases across 4 milestones shipped
- Tests: 350 passing, 91% coverage
- CI/CD: Passing — TruffleHog, Trivy, Semgrep inlined in security check
- ESLint: 0 errors, 108 accepted warnings (console + any)
- Deployment: Local (port 3001) / Vercel (
vercel.jsonconfigured) - GitHub: irishrocker1125/Eliza
Lessons Learned
| Lesson | Recommendation |
|---|---|
| TypeScript strict mode catches errors early | Always use for learning projects |
| Build core features CLI-first | Web interface was trivial after CLI logic was solid |
| Pattern order matters | Document priority carefully; first-match-wins requires ordering discipline |
| Reflection can flip subjects | Avoid reflecting captured groups containing first-person pronouns |
| Dogfooding reveals real issues | Run simulated conversations with emotional personas to catch edge cases |
| Generic fallbacks feel cold | Add emotion-specific fallbacks before generic ones |
| Compliance gaps accumulate silently | Run compliance checklist at every milestone boundary |
Technical Debt
| Item | Priority | Notes |
|---|---|---|
| No persistent conversation history | Low | By design for v1.0, could add later |
| Rules hardcoded in module | Low | Could externalize to JSON for easier editing |
| Fire-and-forget persistence calls | Low | addMessage() errors silently swallowed in server.ts |
Some any types in persona collections | Low | Could tighten type definitions |
Related
- Eliza Engine — planned extraction of the core engine to
@ap/eliza-engine - Topic Memory Pattern —
TopicMemoryclass pattern documented here - Weighted Rule Matching — rule priority system extracted as a reusable pattern
- Factory Pattern with DI
- TypeScript Conventions