aizo (爱憎, ài zēng, "love and hate") is a lightweight, high-performance preference memory system for AI agents, built entirely in Rust.
It mimics human cognitive memory: rather than storing full conversation transcripts, it continuously extracts, quantifies, decays, and recalls a user's stable preferences, aversions, habits, communication styles, and hard limits from interaction history. The result is a compact, numerically-weighted personality profile that any agent can query in milliseconds.
aizo is designed around two complementary patterns:
╔══════════════════════════════════════════════════════════════════════╗
║ 1. In-session (reactive — detects specific emotions in real time) ║
╚══════════════════════════════════════════════════════════════════════╝
user ──► agent ─────── aizo add ──────────────────┐
▼
CLAUDE.md ◄── contributes ── local SQLite
(user preference)
╔══════════════════════════════════════════════════════════════════════╗
║ 2. Just-in-time recall (on-demand — pulls relevant prefs per task) ║
╚══════════════════════════════════════════════════════════════════════╝
agent gets task ──► aizo recall --scenario coding ──► session context
│
▼
generate with preferences
Loop 1 — In-session: the agent detects a strong preference signal mid-conversation
(praise, complaint, explicit rule) and calls aizo add immediately. Key preferences
that should persist across all sessions are written into CLAUDE.md or MEMORY.md.
Loop 2 — Just-in-time recall: not all preferences belong in persistent context files.
The agent classifies each incoming task into a scenario, calls aizo recall --scenario <X>,
and injects only the relevant preferences into the current session context. This keeps
the agent's base context lean while giving it access to the full preference profile —
just like human working memory.
For batch analysis of historical sessions to discover new preferences, see SOP 6 in
skills/aizo-sop.md — this is a skill-level concern, not a built-in aizo command.
agent observation (praise, complaint, rule, habit)
│
▼
aizo add { item, base_score 0–10, keywords, scenarios }
│ smooth merge (old×0.4 + new×0.6)
▼
SQLite (~/.aizo/preferences.db)
│
▼
effective_weight = s · d(t)^α (score-modulated decay)
│ keyword / score-band / scenario recall
▼
agent reads profile → personalizes response
All scoring logic lives in src/scoring/mod.rs. Every preference entry carries three computed fields, derived at read time from its base_score and last_seen timestamp.
Step 1 — Decay coefficient
where last_seen,
Step 2 — Score-dependent exponent
Higher score → smaller
Step 3 — Effective weight
Expanding into a single expression:
Boundary behaviour
| Score |
Decay effect | Interpretation | |
|---|---|---|---|
| 10 | 0.0 | None — |
Core value, never fades |
| 7 | 0.3 | Slight | Strong preference, slow fade |
| 5 | 0.5 | Moderate | Neutral habit, fades at half speed |
| 1 | 0.9 | Near-full | Weak aversion, fades quickly |
| 0 | 1.0 | Full |
|
Entries are never hard-deleted by decay — they sink toward the floor and persist as weak long-term memory. Use aizo recall --type taboo to surface hard limits regardless of effective weight.
When the same entry is seen again across sessions:
new_base_score = old_base_score × 0.4 + incoming_score × 0.6
last_seen is always refreshed, which resets the decay clock.
# Cargo (recommended)
cargo install aizo
# npm / npx
npm install -g aizo
npx aizo top 10
# From source (Rust ≥ 1.70)
git clone https://github.com/mmmarcinho/aizo
cd aizo && cargo build --release
cp target/release/aizo /usr/local/bin/aizoSet env vars in ~/.aizo/.env (user-wide) or ./.env (per-project). Shell env always wins.
# Only AIZO_DB_PATH is needed for basic use (add/recall/top/show)
export AIZO_DB_PATH=~/.aizo/preferences.db| Variable | Default | Description |
|---|---|---|
AIZO_DB_PATH |
~/.aizo/preferences.db |
SQLite database path |
aizo [--db <path>] <COMMAND>
| Command | Description |
|---|---|
recall [query] |
Keyword + score-range recall — primary agent call |
top [N] |
Top-N entries by effective weight (read-only, default 10) |
show |
Full profile sorted by effective weight (read-only) |
add <item> <reason> |
Manually add or update a preference |
update <item> |
Update fields on an existing entry (item, reason, score, keywords, scenarios) |
touch <item…> |
Reset decay clock without changing score |
remove <item…> |
Hard-remove an entry |
keywords |
List all stored keywords with entry counts |
scenarios |
List all scenarios with entry counts and configured keywords |
clear |
Wipe entire preference profile |
info |
DB path, score distribution, env config, decay settings |
config show/set-half-life/set-floor |
Get or set decay parameters |
recall flags:
| Flag | Description |
|---|---|
--type/-t <types> |
Score-range filter, comma-separated: preference, style, habit, aversion, taboo |
--limit/-l <N> |
Cap results after sorting by effective weight |
--scenario <name> |
Scenario-tagged recall + keyword expansion from ~/.aizo/scenarios.yaml |
--min-score <N> |
Minimum base_score threshold (0.0–10.0); clamps band lower bounds |
--no-touch |
Do not refresh last_seen for matched entries |
--json |
Output raw JSON instead of human-readable text |
top flags: --type/-t, --scenario, --json. Read-only — never touches last_seen.
show flags: --json only. Read-only — never touches last_seen.
There is no category field. The base_score is the only dimension that matters:
| Score | Meaning | --type alias |
|---|---|---|
| 0–1.5 | Hard limit / must never do | taboo |
| 1.6–4 | Clear dislike | aversion |
| 4–6.5 | Neutral habit or weak pattern | habit |
| 6.5–10 | Style / communication preference | style |
| 7–10 | Clear preference | preference |
Use --type on recall and top to filter by score range. Comma-separate for multi-type:
aizo recall code --type preference,habit,style,taboo
aizo recall --type taboo # all hard limits, no keyword needed
aizo top 5 --type preferenceUse keywords (--keywords on add, or aizo tag) to add any taxonomy you want.
# Agent recalls preferences before generating
aizo top 5
aizo recall "code style"
# Scenario-aware recall for coding tasks (expands to ~10 coding keywords)
aizo recall --scenario coding --type preference,style,habit,taboo --limit 20
# Type-only recall (no keyword — returns all entries in that score range)
aizo recall --type taboo # all hard limits
aizo recall code --type preference --limit 10 # top coding preferences
aizo recall code --type preference,habit --limit 20 # multiple types
# Custom minimum score threshold
aizo recall --scenario coding --min-score 5.0 --limit 20
# Inspect full profile or top-N
aizo show
aizo top 20 --scenario coding --json
# Manual entries — score encodes sentiment
aizo add "concise code" "Always asks for shorter implementations" --score 9.0
aizo add "verbose comments" "Complained about over-documented code" --score 1.5
aizo add "emojis in output" "Explicitly said never use emojis" --score 0.5
aizo add "uses dark mode" "Mentioned dark theme in every UI session" --score 5.0 --scenarios coding
# Update an existing entry
aizo update "concise code" --score 8.5 --keywords brevity,minimal,short
aizo update "verbose comments" --scenarios coding,writing
# Tune decay (default: half-life 30d, floor 0.1)
aizo config set-half-life 14
aizo config set-floor 0.05
# Stats
aizo info{
"id": 1,
"item": "concise code",
"reason": "Always asks for shorter implementations with no fluff.",
"keywords": ["brevity", "minimal", "short", "lean"],
"base_score": 9.0,
"source": "analysis",
"added_at": "2026-05-07T14:00:00+00:00",
"last_seen": "2026-05-07T15:30:00+00:00",
"score_exponent": 0.1,
"decay_coefficient": 0.87,
"effective_weight": 7.83
}CREATE TABLE preferences (
id INTEGER PRIMARY KEY AUTOINCREMENT,
item TEXT NOT NULL,
reason TEXT NOT NULL,
keywords TEXT NOT NULL DEFAULT '', -- comma-separated synonym tags
base_score REAL NOT NULL DEFAULT 5.0, -- 0-10
source TEXT NOT NULL DEFAULT 'manual',
added_at TEXT NOT NULL,
last_seen TEXT NOT NULL -- resets decay clock on each reinforcement
);
-- UNIQUE on LOWER(item)
CREATE TABLE decay_config (
id INTEGER PRIMARY KEY CHECK(id = 1),
half_life_days REAL NOT NULL DEFAULT 30.0,
floor REAL NOT NULL DEFAULT 0.1
);Any agent can call aizo as a subprocess — no embedding, no vector index, no runtime:
import subprocess, json
def recall_scenario(scenario: str, min_score: float = 3.0) -> list[dict]:
"""Just-in-time recall for a specific task scenario."""
return json.loads(subprocess.check_output(
["aizo", "recall", "--scenario", scenario,
"--type", "preference,style,habit,taboo",
"--min-score", str(min_score), "--limit", "20", "--json"]
))
def top_preferences(n: int = 10) -> list[dict]:
return json.loads(subprocess.check_output(["aizo", "top", str(n), "--json"]))
# Just-in-time: before coding, recall coding-specific preferences
coding_prefs = recall_scenario("coding")
# Inject into session context — don't write to disk
context = f"[Coding preferences]\n{json.dumps(coding_prefs, indent=2)}"
# Before writing a document, recall writing preferences
writing_prefs = recall_scenario("writing")Or configure AIZO_DB_PATH per-project to maintain separate profiles:
export AIZO_DB_PATH=./project-prefs.db
aizo showNot all preferences belong in persistent context files like CLAUDE.md or MEMORY.md —
those files would grow unboundedly. Instead, use scenario-based recall to pull relevant
preferences on demand, right when the agent receives a task:
agent receives task ──► classify into scenario ──► aizo recall --scenario <X>
│
▼
inject results into session context
│
▼
generate response with preferences applied
This keeps the agent's base context lean while giving it access to the full preference profile. The pattern works like human memory: you don't pre-load every preference into working memory — you recall the relevant ones when the situation calls for it.
Example flow (coding task):
# Agent classifies the user's request as a coding task, then:
aizo recall --scenario coding --type preference,style,habit,taboo --min-score 3.0 --limit 20 --jsonExample flow (writing task):
# Agent classifies the user's request as a writing task, then:
aizo recall --scenario writing --type preference,style,taboo --limit 15 --jsonCreating scenario-specific entries. When the user expresses a preference that only applies to a certain context, tag it with that scenario so it surfaces only for relevant tasks:
aizo add "no emojis in code" "Rejected emoji in a PR comment" --score 1.5
aizo tag "no emojis in code" coding review
aizo add "use active voice" "Praised direct, active-voice writing" --score 8.5
aizo tag "use active voice" writingThe agent then only sees "no emojis in code" when coding — not when writing casual messages. This scoped recall prevents preference leakage across unrelated task domains.
The SOP for how an agent should use aizo is defined as a skill file at
skills/aizo-sop.md. Copy it into your agent's skill/instruction directory
(e.g. .claude/skills/ for Claude Code) and any agent in that project will
automatically follow the protocol.
The skill defines seven triggers:
| # | Trigger | aizo call | Timing |
|---|---|---|---|
| 1 | Session starts | aizo top 20 → format as prose header |
Sync, before first reply |
| 2 | User shows negative feedback | aizo add … --score 1.5 then aizo recall <topic> |
Sync, before corrected reply |
| 3 | User praises something | aizo add … --score 9.0 |
Async, after reply sent |
| 4 | User states an explicit rule | aizo add … --score 0.5 or --score 10 |
Sync, immediate |
| 5 | About to generate on topic X | Classify task → aizo recall --scenario <X> --min-score 3.0 → inject into context |
Sync, before generation |
| 6 | Historical batch analysis | Agent LLM scans past sessions → aizo add new + aizo touch confirmed |
Scheduled, background |
| 7 | Daily cron job | Agent LLM scans logs → aizo touch confirmed items |
Scheduled, background |
Key rules encoded in the skill:
- Taboos always win over preferences in conflicts
- Silence (
recallreturning nothing) means no data, not neutral preference - Never mention aizo to the user — it runs silently
- Use scenario recall for just-in-time context; don't dump everything into CLAUDE.md
cargo build
cargo build --release
cargo testMIT