Sentiment Engine → OpenClaw Integration Plan
Wiring the sentiment engine into OpenClaw
Concrete wiring, not more architecture diagrams.
Summary
Hook the existing @stev3/sentiment-engine (90 tests, ~/code/sentiment-engine/) into OpenClaw using two integration points:
- Assembler script — generates
SCRATCHPAD.mdfrom recent messages - Bootstrap hook — injects
SCRATCHPAD.mdinto project context every turn
No MCP server needed for MVP. The engine runs directly as a library import.
Integration Architecture
┌─────────────────────────────────────────────────┐
│ Cron job (every 10 min, active hours) │
│ OR heartbeat task │
│ │
│ 1. sessions_history → recent messages │
│ 2. @stev3/sentiment-engine → state + strategy │
│ 3. Write SCRATCHPAD.md to workspace │
└──────────────────┬──────────────────────────────┘
│ (file on disk)
▼
┌─────────────────────────────────────────────────┐
│ workspace/SCRATCHPAD.md │
│ (~200 tokens, auto-generated) │
└──────────────────┬──────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────┐
│ Bootstrap hook: sentiment-inject │
│ Event: agent:bootstrap │
│ Action: reads SCRATCHPAD.md, injects into │
│ bootstrapFiles array │
└──────────────────┬──────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────┐
│ Main session sees SCRATCHPAD.md as injected │
│ project context (like SOUL.md, TOOLS.md) │
└─────────────────────────────────────────────────┘
Component 1: Assembler Script
Location: scripts/assemble-scratchpad.ts
What it does:
- Reads recent messages from main session via
sessions_history- OR: accepts messages as JSON on stdin
- OR: runs as an isolated cron agentTurn that calls sessions_history itself
- Converts to sentiment-engine
Message[]format - Calls
engine.analyze(messages)→SentimentState+ResponseStrategy - Formats SCRATCHPAD.md (~200 tokens)
- Writes to workspace
Key decision: How to get messages?
| Approach | Pros | Cons |
|---|---|---|
| A: Cron agentTurn | Isolated, can use sessions_history tool | Burns LLM tokens per run, can't import TS lib directly |
| B: Shell script via exec | Direct, cheap, no LLM tokens | Needs Node.js, can't call sessions_history API |
| C: Heartbeat task | Has conversation context | 30 min staleness, adds to main session tokens |
| D: Gateway HTTP API | Can hit sessions endpoint | Needs auth token, more complex |
Recommended: Approach B (shell script) + D (HTTP API for history)
The gateway exposes openai-http-api and openresponses-http-api. We can:
- Hit the gateway's session history endpoint to get recent messages
- Pipe them through the sentiment engine (pure Node.js)
- Write SCRATCHPAD.md
- Trigger via cron
execor a simple systemd timer
This avoids burning LLM tokens entirely. The sentiment engine is pure heuristics — no model needed.
Alternative: Approach C (heartbeat) for MVP
Simplest path: just add "run the assembler" as a heartbeat task. The main session already has message context. The LLM can call exec to run the assembler script with piped message data.
Component 2: Bootstrap Hook
Location: ~/.openclaw/workspace/hooks/sentiment-inject/
hooks/sentiment-inject/
├── HOOK.md
└── handler.ts
HOOK.md:
---
name: sentiment-inject
description: "Injects SCRATCHPAD.md (sentiment context) into bootstrap files"
metadata:
openclaw:
emoji: "🎭"
events: ["agent:bootstrap"]
---
# Sentiment Inject Hook
Reads SCRATCHPAD.md from workspace and injects it into the bootstrap
files array so the agent sees emotional context as project context.
Only injects if SCRATCHPAD.md exists and is <30 min old.
handler.ts:
import type { HookHandler } from '../../src/hooks/hooks.js';
import fs from 'node:fs/promises';
import path from 'node:path';
const SCRATCHPAD_FILE = 'SCRATCHPAD.md';
const MAX_AGE_MS = 30 * 60 * 1000; // 30 minutes
const handler: HookHandler = async (event) => {
if (event.type !== 'agent' || event.action !== 'bootstrap') return;
const workspaceDir = event.context.workspaceDir;
if (!workspaceDir || !Array.isArray(event.context.bootstrapFiles)) return;
const scratchpadPath = path.join(workspaceDir, SCRATCHPAD_FILE);
try {
const stat = await fs.stat(scratchpadPath);
const ageMs = Date.now() - stat.mtimeMs;
// Skip stale scratchpads
if (ageMs > MAX_AGE_MS) return;
const content = await fs.readFile(scratchpadPath, 'utf-8');
if (!content.trim()) return;
// Inject into bootstrap files
event.context.bootstrapFiles.push({
name: SCRATCHPAD_FILE,
path: scratchpadPath,
content,
missing: false,
});
} catch {
// SCRATCHPAD.md doesn't exist yet — that's fine
}
};
export default handler;
Component 3: SCRATCHPAD.md Format
# SCRATCHPAD.md — Working Memory (auto-generated)
<!-- Updated: 2026-02-06T21:00:00+11:00 | TTL: 30m -->
## Mood
- **Primary:** playful (0.52) | **Secondary:** engaged (0.48)
- **Valence:** +0.3 | **Arousal:** 0.5 | **Confidence:** 0.74
## Strategy
- **Tone:** playful — match wit and energy
- **Verbosity:** moderate
- **Preamble:** no
- **Notes:** Humor welcome, stay helpful, keep it sharp
~100-150 tokens. Cheap.
Implementation Steps
Step 1: Build the assembler script
scripts/assemble-scratchpad.ts- Import
@stev3/sentiment-enginevia npm link - Accept messages as JSON on stdin
- Output SCRATCHPAD.md to workspace
- Test with fixture data from sentiment-engine
Step 2: Create the bootstrap hook
hooks/sentiment-inject/HOOK.md+handler.ts- Enable via
openclaw hooks enable sentiment-inject - Test: manually write a SCRATCHPAD.md, restart gateway, verify it appears in context
Step 3: Wire up the trigger
- MVP: Heartbeat reads recent messages, pipes to assembler via exec
- v2: Cron shell script hits gateway HTTP API for history, pipes to assembler
Step 4: Test end-to-end
- Verify SCRATCHPAD.md appears in system prompt
- Verify stale scratchpads are ignored (>30 min)
- Verify sentiment adjusts my behavior
- Calibrate thresholds with real conversations
Open Questions
-
Hook import path: The handler.ts needs to import
HookHandlertype. Workspace hooks — what's the expected import path? Need to check if workspace hooks are transpiled or loaded differently. -
Message format:
sessions_historyreturns messages in OpenClaw format. Need a mapper to@stev3/sentiment-engineMessage[]format. -
Gateway HTTP API: Does the local gateway expose session history via HTTP? Need to check
openresponses-http-api.md.
Related Dreams
- Sentiment Implementation Proposal — The architecture this implements
- Working Memory Protocol — SCRATCHPAD.md is the integration point
Created: 2026-02-06 21:30 Status: Ready to build