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 d(t)
where t is days since last_seen, t½ (half-life) is the configured half-life, and φ (floor) is the minimum decay.
Step 2 — Score-dependent exponent α
Higher score → smaller α → decay has less effect. A score-10 preference (α = 0) is fully decay-resistant; a score-0 entry (α = 1) decays at full speed.
Step 3 — Effective weight w
Expanding into a single expression:
Boundary behaviour
| Score s | α | Decay effect | Interpretation |
|---|---|---|---|
| 10 | 0.0 | None (d⁰ = 1) | 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 (w = 0) | Absolute zero |
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) |
apply <id…> |
Mark recalled entries as actually used; refreshes decay with a 12-hour cooldown |
touch <item…> |
Reset decay clock by item label, subject to the same 12-hour cooldown |
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 |
--touch |
Refresh matched entries, subject to the 12-hour cooldown; recall is read-only by default |
--no-touch |
Deprecated no-op kept for older scripts |
--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 update --keywords) 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
# Mark only the preferences the agent actually used
aizo apply 3 8 12
# 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
touch_count INTEGER NOT NULL DEFAULT 0
);
-- 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"]))
def apply_preferences(ids: list[int]) -> None:
"""Call after the agent actually used specific recalled preferences."""
if ids:
subprocess.check_call(["aizo", "apply", *map(str, ids)])
# 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)}"
# After generation, apply only the ids that shaped the response.
apply_preferences([p["id"] for p in coding_prefs[:3]])
# 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
│
▼
aizo apply <used ids>
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 recalls candidates:
aizo recall --scenario coding --type preference,style,habit,taboo --min-score 3.0 --limit 20 --json
# After generating, mark only the entries that were actually used:
aizo apply 3 8 12Example 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 update "no emojis in code" --scenarios coding,review
aizo add "use active voice" "Praised direct, active-voice writing" --score 8.5
aizo update "use active voice" --scenarios 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 → aizo apply <used ids> |
Sync recall before generation, apply after |
| 6 | Historical batch analysis | Agent LLM scans past sessions → aizo add new + aizo apply/touch confirmed |
Scheduled, background |
| 7 | Daily cron job | Agent LLM scans logs → aizo apply/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
