# kokoro-chat — Claude Code Guide

## What This Is

Character chat engine core — extracted from roleplay-chat.
Protocol-based storage, dependency-injectable ChatEngine. Zero framework dependencies.

**Stack:** Python 3.11+ · hatch · motosan-ai · motosan-chat · PyYAML

**Key dirs:**
```
kokoro_chat/
├── character/     # Character model + YAML V2 loader
├── lorebook/      # Keyword-triggered context injection
├── attitude/      # Affection/trust tracking + time decay
├── memory/        # Memo stale-filtering logic
├── prompt/
│   ├── assembler.py  # System prompt assembly (static + full paths)
│   ├── sections.py   # Shared section builders (single source of truth)
│   ├── budget.py     # Token budget constants + estimator
│   └── formatter.py  # Conversation history formatting
├── providers/     # Context providers (attitude, persona, recent_chat)
├── tools/         # LLM tools (update_attitude, save_memory, upsert_memo, search_memories)
├── storage/       # Protocol interfaces (9 runtime_checkable Protocols)
├── engine/
│   ├── chat.py              # ChatEngine (orchestration only)
│   ├── llm.py               # LLM client factory + bridge classes
│   ├── attitude_manager.py  # AttitudeManager (cache → store → decay)
│   ├── message_manager.py   # MessageManager (history + save + few-shot)
│   ├── summarizer.py        # Summarizer (turn counting + LLM compression)
│   └── background.py        # Background task prompts + parsers
└── config.py      # ChatConfig dataclass
```

---

## Architecture

```
User Message
    │
    ▼
① ChatEngine.chat_stream()
    ├→ AttitudeManager.get()    [cache → store → new]
    ├→ MessageManager.get_for_prompt() [cache → store]
    ├→ assemble_static_system_prompt()
    └→ MessageManager.build_few_shot_prefix()
    │
    ▼
② AgentLoop (motosan-chat) — tool calling loop
    Tools: update_attitude / save_memory / upsert_memo / search_memories
    │
    ▼
③ Async stream → caller (text_delta / message_complete events)
    │
    ▼
④ Background Tasks (semaphore-controlled)
    ├→ AttitudeManager.decay_modifiers()
    └→ Summarizer.maybe_summarize()  — rolling summary every N turns
```

## Storage Protocols

All persistence is via `typing.Protocol` (`@runtime_checkable`). App layer injects concrete implementations.

| Protocol | Purpose |
|----------|---------|
| `AttitudeStore` | Get/create/save attitude state |
| `ChatHistoryStore` | Fetch recent messages |
| `MemoryVectorStore` | Semantic search + add memories |
| `MemoStore` | Key-value user info memos |
| `RollingSummaryStore` | Compressed conversation history |
| `EventStore` | Character/user events |
| `UsageLogStore` | LLM token/cost logging |
| `ChatMessageWriter` | Persist new messages |
| `CacheStore` | Fast cache (Redis/in-memory) |

## Character System

- **YAML V2 Character Cards** with Kokoro extensions
- `character/loader.py`: `map_v2_yaml_to_db()` — YAML → DB model kwargs
- `character/model.py`: `Character.from_db_model()` — DB → dataclass
- Kokoro extensions: `speech_patterns_v2`, `affection_overrides`, `post_history_by_level`, `anti_patterns`

## Attitude System

- `affection` (0-100), `trust` (0-100), `mood` (string)
- 5 levels: stranger → friend → crush → lover → married
- Time decay after 3+ days inactivity
- Event-triggered modifiers with remaining-turn countdown
- Level-specific behavior text via `affection_overrides`

## Dependencies

| Package | Purpose |
|---------|---------|
| `motosan-ai` | LLM client (Anthropic, OpenAI, MiniMax) |
| `motosan-chat` | AgentLoop, ContextProvider, Tool framework |
| `pyyaml` | YAML V2 character card parsing |

---

## Common Commands

```bash
# Install
pip install -e ".[dev]"

# Run tests
pytest -x -q

# Type check (if added)
mypy kokoro_chat/
```

---

## Critical Rules

- **Protocol-only storage** — never import SQLAlchemy, Redis, or any DB library in this package
- **No framework deps** — no FastAPI, Flask, Django
- **Async-first** — all store methods and engine methods are async
- **motosan-ai for LLM** — never call provider APIs directly
- **Chinese prompts** — default platform rules and attitude behaviors are in Traditional Chinese
- **Token budget** — system prompt must stay within 12000 tokens; degrade gracefully

---

## Known Patterns

- `_estimate_tokens(text)` uses `len(text) // 2` — rough CJK/English mix estimate
- Prompt assembly degrades gracefully: drops memories → events → truncates memos when over budget
- `mes_example` limited to 1 by default (`DEFAULT_MAX_MES_EXAMPLES`)
- Background summarization uses a cheaper model (`memory_llm_model`, default Haiku)
- Lorebook token budget: `len(content) // 4` estimate
