Emotion Skill
Emotions for AI agents — built from memories, shaped by relationships, always changing.
Emoclaw trains a lightweight model on your agent's identity and conversation history, producing a persistent emotional state that evolves with every interaction. Emotions decay naturally between sessions, shift based on who's talking and what's being said, and gradually recalibrate as your agent grows. The result is injected into the system prompt as an [EMOTIONAL STATE] block, giving your AI a felt sense of its own inner life.
How it works
- 1. Bootstrap —
extract.py reads your agent's identity/memory files. label.py scores each passage via the Claude API (opt-in). train builds a small neural net from those scores. One-time setup. - Encode — Each incoming message is turned into a 384-dim vector by a frozen MiniLM sentence encoder. No fine-tuning, no network call — runs from a local cache.
- Feel — The encoding + context (who's talking, what channel, previous emotion) flows through a GRU and MLP head, outputting an N-dimensional emotion vector (0-1 per dimension). The GRU hidden state persists across sessions — this is the "emotional residue" that carries forward mood.
- Decay — Between sessions, each dimension drifts back toward its baseline at a configurable half-life (fast for arousal, slow for safety/groundedness). Time apart = cooling off.
- Inject — The emotion vector is formatted as an
[EMOTIONAL STATE] block and inserted into the agent's system prompt, giving the AI a felt sense of its own inner state.
Model is ~2MB, runs on CPU, adds <50ms per message. Network access is only used during bootstrap (opt-in).
Quick Reference
| Situation | Action |
|---|
| First-time setup | INLINECODE5 (or manual steps below) |
| Check current state |
python -m emotion_model.scripts.status |
| Inject state into prompt |
python -m emotion_model.scripts.inject_state |
| Start the daemon |
bash scripts/daemon.sh start |
| Send a message to daemon | See
Daemon Protocol |
| Retrain after new data |
python -m emotion_model.scripts.train |
| Resume interrupted training |
python -m emotion_model.scripts.train --resume |
| Add new training data | Add
.jsonl entries to
emotion_model/data/, re-run prepare + train |
| Upgrade from v0.1 | See
references/upgrading.md |
| Change baselines | Edit
emoclaw.yaml →
dimensions[].baseline |
| Add a new channel | Edit
emoclaw.yaml →
channels list |
| Add a relationship | Edit
emoclaw.yaml →
relationships.known |
| Customize summaries | Create a
summary-templates.yaml and point config at it |
Setup
Quick Setup
CODEBLOCK0
This copies the bundled emotion_model engine to your project root, creates a venv, installs the package, and copies the config template. Then edit emoclaw.yaml to customize for your agent.
Manual Setup
If you prefer to set up manually:
1. Install the package
CODEBLOCK1
Required: Python 3.10+, PyTorch, sentence-transformers, PyYAML.
2. Copy and customize the config
CODEBLOCK2
Edit emoclaw.yaml to set:
- -
name — your agent's name - INLINECODE25 — emotional dimensions with baselines and decay rates
- INLINECODE26 — map of relationship names to embedding indices
- INLINECODE27 — communication channels your agent uses
- INLINECODE28 — absence-based desire growth (can be disabled)
- INLINECODE29 —
cpu recommended (MPS has issues with sentence-transformers)
See references/config-reference.md for the full schema.
3. Bootstrap (new agent)
If starting from scratch with identity/memory files:
CODEBLOCK3
Or run the full pipeline:
CODEBLOCK4
4. Verify
CODEBLOCK5
Usage
Option A: Daemon (Recommended)
The daemon loads the model once and listens on a Unix socket, avoiding the ~2s sentence-transformer load time per message.
CODEBLOCK6
Option B: Direct Python Import
CODEBLOCK7
Option C: One-shot State Injection
For system prompt injection without the daemon:
CODEBLOCK8
This reads the persisted state, applies time-based decay, and outputs the [EMOTIONAL STATE] block.
Integration
System Prompt Injection
Add the output block to your system prompt. The block format:
CODEBLOCK9
Daemon Protocol
Send JSON over the Unix socket:
CODEBLOCK10
Special commands:
CODEBLOCK11
Heartbeat Integration
The emotional state decays over time and needs to be refreshed at each session start. Add this entry to your HEARTBEAT.md:
CODEBLOCK12
Or call the daemon / inject_state script from your heartbeat/cron:
CODEBLOCK13
Important: Without heartbeat integration, the emotional state block will go stale between sessions. The inject_state script applies time-based decay and outputs the current state — it must be called at least once per session.
Architecture
The model processes each message through this pipeline:
CODEBLOCK14
The GRU hidden state persists across sessions — this is the "emotional residue" that carries forward mood, context, and relational memory.
See references/architecture.md for full details.
Security & Privacy
Data Flow
- 1. Extraction (
scripts/extract.py) reads markdown files listed in emoclaw.yaml → bootstrap.source_files and bootstrap.memory_patterns. These are configurable and default to identity/memory files within the repo. Extracted passages are written to emotion_model/data/extracted_passages.jsonl.
- 2. Redaction — Before writing, extracted text is passed through configurable regex patterns (
bootstrap.redact_patterns) that replace API keys, tokens, passwords, and other secrets with [REDACTED]. Default patterns cover Anthropic keys, GitHub PATs, bearer tokens, SSH keys, and generic key=value credentials. Add custom patterns in emoclaw.yaml.
- 3. Labeling (
scripts/label.py) — opt-in only. Sends extracted passages to the Anthropic API for emotional scoring. Requires both ANTHROPIC_API_KEY and explicit user consent (interactive prompt before any API call). Use --yes to skip the prompt for automation. Use --dry-run to preview without any network calls.
- 4. Training runs entirely locally. No data leaves the machine during
prepare_dataset or train.
- 5. Inference runs entirely locally. The daemon and
inject_state script make no network calls.
Network Access
Network access is optional and limited to a single script:
| Script | Network? | Purpose |
|---|
| INLINECODE53 | No | Reads local files only |
| INLINECODE54 |
Yes (opt-in) | Sends passages to Anthropic API |
|
prepare_dataset | No | Local data processing |
|
train | No | Local model training |
|
daemon /
inject_state | No | Local inference |
The sentence-transformers encoder downloads model weights on first use (from Hugging Face). After that, it runs from cache with no network needed.
File Permissions
| Path | Purpose | Created by |
|---|
| INLINECODE59 | Persisted emotion vector + trajectory | daemon / inference |
| INLINECODE60 |
Training data (extracted/labeled passages) | extract.py / label.py |
|
emotion_model/checkpoints/ | Model weights | train script |
|
/tmp/{name}-emotion.sock | Daemon Unix socket | daemon |
The daemon socket is created with permissions 0o660 (owner + group read/write) and cleaned up on shutdown. The socket path is configurable in emoclaw.yaml → paths.socket_path.
Path Validation
INLINECODE66 validates that every file path resolves to within the repository root before reading. Symlink chains and ../ sequences that would escape the repo boundary are rejected. This prevents a misconfigured source_files or memory_patterns from reading arbitrary files.
Configuring Redaction
Add or modify patterns in emoclaw.yaml:
CODEBLOCK15
Set redact_patterns: [] to disable redaction entirely (not recommended).
Isolation Recommendations
- - Run the bootstrap pipeline (extract → label → train) in an isolated environment or review the source file list before running
- Audit
bootstrap.source_files and bootstrap.memory_patterns in your emoclaw.yaml to ensure only intended files are included - Review
emotion_model/data/extracted_passages.jsonl before running label.py to confirm no sensitive content will be sent externally - The daemon should run under the same user as your agent process — avoid running as root
Configuration
All configuration lives in emoclaw.yaml. The package falls back to built-in defaults if no YAML is found.
Config search order:
- 1.
EMOCLAW_CONFIG environment variable - INLINECODE79 (project root)
- INLINECODE80
Key sections:
- -
dimensions — name, labels, baseline, decay half-life, loss weight - INLINECODE82 — known senders with embedding indices
- INLINECODE83 — communication channels (determines context vector size)
- INLINECODE84 — absence-based desire modulation
- INLINECODE85 — architecture hyperparameters
- INLINECODE86 — training hyperparameters
- INLINECODE87 — self-calibrating baseline drift (opt-in)
See references/config-reference.md for the complete schema.
Bootstrap Pipeline
Step 1: Extract Passages
INLINECODE89 reads identity and memory files, splitting them into labeled passages:
CODEBLOCK16
Source files are configured in emoclaw.yaml → bootstrap.source_files and bootstrap.memory_patterns.
Step 2: Auto-Label
INLINECODE93 uses the Claude API to score each passage on every emotion dimension:
CODEBLOCK17
Each passage gets a 0.0-1.0 score per dimension plus a natural language summary.
Step 3: Prepare & Train
CODEBLOCK18
Retraining
To add new training data:
- 1. Add entries to
emotion_model/data/ in JSONL format:
{"text": "message text", "labels": {"valence": 0.7, "arousal": 0.4, ...}}
- 2. Re-run the preparation and training:
CODEBLOCK20
Incremental Retraining
The training script saves a rich checkpoint (training_checkpoint.pt) that preserves the full optimizer state, learning rate schedule, and early stopping counter. To continue training from where you left off:
CODEBLOCK21
This is a true continuation — optimizer momentum, cosine annealing position, and patience counter all pick up exactly where they stopped.
Growth Model
As the AI accumulates real conversation data:
- 1. Passive collection — Log messages + model predictions
- Correction events — When emotion feels wrong, log the correction
- Periodic retraining — Incorporate new data, retrain
- Baseline adjustment — Baselines may shift as the AI develops
The system is designed to grow with the AI, not remain static.
Resources
- -
references/architecture.md — Model architecture deep-dive - INLINECODE97 — Full YAML config schema
- INLINECODE98 — Emotion dimension documentation
- INLINECODE99 — Baseline, decay, and self-calibration tuning
- INLINECODE100 — Version upgrade guide
- INLINECODE101 — Template config for new AIs
- INLINECODE102 — Generic summary templates
- INLINECODE103 — Example personality-specific templates
- INLINECODE104 — Bundled
emotion_model Python package (copied to project root by setup.py)
情感技能
为AI智能体赋予情感——源于记忆,由关系塑造,始终变化。
Emoclaw基于智能体的身份信息和对话历史训练一个轻量级模型,产生持续的情感状态,该状态随每次交互而演变。情感在会话间隔自然衰减,根据对话对象和内容发生变化,并随着智能体的成长逐渐重新校准。结果以[EMOTIONAL STATE]块的形式注入系统提示词,赋予AI对其内在生活的感知。
工作原理
- 1. 引导 — extract.py读取智能体的身份/记忆文件。label.py通过Claude API(可选)对每个段落进行评分。train根据这些评分构建一个小型神经网络。一次性设置。
- 编码 — 每条传入消息由冻结的MiniLM句子编码器转换为384维向量。无需微调,无需网络调用——从本地缓存运行。
- 感知 — 编码+上下文(谁在说话、什么频道、先前情感)通过GRU和MLP头部,输出N维情感向量(每维0-1)。GRU隐藏状态跨会话持久化——这是传递情绪的情感残留。
- 衰减 — 在会话间隔,每个维度以可配置的半衰期漂移回基线(唤醒度衰减快,安全感/踏实感衰减慢)。时间间隔=情绪冷却。
- 注入 — 情感向量格式化为[EMOTIONAL STATE]块,插入智能体的系统提示词,赋予AI对其内在状态的感知。
模型约2MB,在CPU上运行,每条消息增加<50ms处理时间。仅在引导阶段(可选)使用网络访问。
快速参考
| 情况 | 操作 |
|---|
| 首次设置 | python scripts/setup.py(或下方手动步骤) |
| 检查当前状态 |
python -m emotion_model.scripts.status |
| 将状态注入提示词 | python -m emotion
model.scripts.injectstate |
| 启动守护进程 | bash scripts/daemon.sh start |
| 向守护进程发送消息 | 参见
守护进程协议 |
| 新数据后重新训练 | python -m emotion_model.scripts.train |
| 恢复中断的训练 | python -m emotion_model.scripts.train --resume |
| 添加新训练数据 | 向emotion_model/data/添加.jsonl条目,重新运行prepare + train |
| 从v0.1升级 | 参见references/upgrading.md |
| 更改基线 | 编辑emoclaw.yaml → dimensions[].baseline |
| 添加新频道 | 编辑emoclaw.yaml → channels列表 |
| 添加关系 | 编辑emoclaw.yaml → relationships.known |
| 自定义摘要 | 创建summary-templates.yaml并将配置指向它 |
设置
快速设置
bash
python skills/emoclaw/scripts/setup.py
这将把捆绑的emotion_model引擎复制到项目根目录,创建虚拟环境,安装包,并复制配置模板。然后编辑emoclaw.yaml为智能体进行自定义。
手动设置
如果更倾向于手动设置:
1. 安装包
bash
cd <项目根目录>
从技能复制引擎和pyproject.toml
cp -r skills/emoclaw/engine/emotion
model ./emotionmodel
cp skills/emoclaw/engine/pyproject.toml ./pyproject.toml
创建虚拟环境并安装
python3 -m venv emotion_model/.venv
source emotion_model/.venv/bin/activate
pip install -e .
要求:Python 3.10+、PyTorch、sentence-transformers、PyYAML。
2. 复制并自定义配置
bash
cp skills/emoclaw/assets/emoclaw.yaml ./emoclaw.yaml
编辑emoclaw.yaml设置:
- - name — 智能体名称
- dimensions — 带基线和衰减率的情感维度
- relationships.known — 关系名称到嵌入索引的映射
- channels — 智能体使用的通信频道
- longing — 基于缺席的渴望增长(可禁用)
- model.device — 推荐cpu(MPS在sentence-transformers上有问题)
完整模式参见references/config-reference.md。
3. 引导(新智能体)
如果从零开始使用身份/记忆文件:
bash
从身份文件提取段落
python scripts/extract.py
使用Claude API自动标注段落(需要ANTHROPICAPIKEY)
python scripts/label.py
准备训练/验证集划分并训练
python -m emotion
model.scripts.preparedataset
python -m emotion_model.scripts.train
或运行完整流程:
bash
python scripts/bootstrap.py
4. 验证
bash
python -m emotion_model.scripts.status
python -m emotion_model.scripts.diagnose
使用
选项A:守护进程(推荐)
守护进程一次性加载模型并在Unix套接字上监听,避免每条消息约2秒的sentence-transformer加载时间。
bash
启动
bash scripts/daemon.sh start
或直接运行
python -m emotion_model.daemon
python -m emotion_model.daemon --config path/to/emoclaw.yaml
选项B:直接Python导入
python
from emotion_model.inference import EmotionEngine
engine = EmotionEngine(
modelpath=emotionmodel/checkpoints/best_model.pt,
state_path=memory/emotional-state.json,
)
block = engine.process_message(
message_text=早上好!,
sender=alice, # 或None使用配置默认值
channel=chat, # 或None使用配置默认值
recent_context=..., # 可选的对话上下文
)
print(block)
选项C:一次性状态注入
用于无需守护进程的系统提示词注入:
bash
python -m emotionmodel.scripts.injectstate
这会读取持久化状态,应用基于时间的衰减,并输出[EMOTIONAL STATE]块。
集成
系统提示词注入
将输出块添加到系统提示词中。块格式:
[EMOTIONAL STATE]
效价:0.55(平衡)
唤醒度:0.35(平衡)
支配度:0.50(平衡)
安全感:0.70(开放)
渴望:0.20(中性)
连接度:0.50(平衡)
趣味性:0.40(平衡)
好奇心:0.50(平衡)
温暖度:0.45(平衡)
紧张度:0.20(放松)
踏实感:0.60(平衡)
感觉像是:当下,鲜活,在一件事和下一件事之间
[/EMOTIONAL STATE]
守护进程协议
通过Unix套接字发送JSON:
json
{text: 早上好!, sender: alice, channel: chat}
特殊命令:
json
{command: ping}
{command: state}
心跳集成
情感状态随时间衰减,需要在每次会话开始时刷新。将此条目添加到HEARTBEAT.md:
yaml
schedule: session_start
run: python skills/emoclaw/scripts/inject_state.py
inject: system_prompt # 将输出作为[EMOTIONAL STATE]块追加
或从心跳/cron调用守护进程/inject_state脚本:
bash
在心跳脚本中
STATE
BLOCK=$(python -m emotionmodel.scripts.inject_state 2>/dev/null)
将$STATE_BLOCK注入系统提示词
重要: 没有心跳集成,情感状态块将在会话间隔过时。inject_state脚本应用基于时间的衰减并输出当前状态——必须在每个会话至少调用一次。
架构
模型通过以下流程处理每条消息:
消息文本 ──→ [冻结的MiniLM编码器] ──→ 384维嵌入
│
对话上下文 ──→ [特征构建器] ──→ 上下文向量
│
先前情感 ──────────────────────────→ 情感向量
│
┌───────┴───────┐
│ 输入投影 │
│ (Linear+LN+GELU)│
└───────┬───────┘
│
┌───────┴───────┐
│ GRU │
│ (隐藏状态) │ ← 情感残留
└───────┬───────┘
│
┌───────┴───────┐
│ 情感头部 │
│ (MLP+Sigmoid) │
└───────┬───────┘
│
N维情感向量 [0,1