Install
openclaw skills install personality-engineSix-system behavior engine that makes any OpenClaw agent feel alive. Editorial voice injects opinions. Selective silence knows when NOT to talk. Variable tim...
openclaw skills install personality-engineGoal: Make any AI agent feel alive — opinions, awareness, judgment, memory, timing sense, and engagement sensitivity. Works with trading agents, notification systems, personal assistants, or any proactive agent. Not just data delivery.
Trigger fires → engine.py orchestrator
↓
selective_silence (should we stay silent?)
↓
urgency_compute (how urgent is this 0.0-1.0?)
↓
engagement_modifier (adjust for user response patterns)
↓
variable_timing (schedule delivery based on urgency + time of day)
↓
context_buffer (add back-references to earlier messages today)
↓
editorial_voice (inject personality / opinions)
↓
dedup (avoid repeats within rolling window)
↓
send → iMessage (or other transport)
Plus two ambient systems:
What: Each trigger type gets a personality — opinions that vary based on market state, portfolio P&L, signal confidence.
Per-trigger voice pools:
Customization point: Add trigger types by extending the VOICE_POOLS dict in editorial_voice.py. Each entry maps (trigger_name, market_state) → list of opinion strings.
What: Not every trigger fire deserves a message. Silent skips are explicit: "Skipped the brief — nothing worth your attention."
Content quality checks per trigger:
Silence cadence:
"Skipped the brief — nothing worth your attention."Customization point: Adjust thresholds in selective_silence.py:
SILENCE_THRESHOLDS = {
'vol_floor': 0.5, # % vol threshold for morning silence
'divergence_age_limit': 3, # hours
'signal_confidence_floor': 0.65,
'edge_floor': 1.0, # %
'portfolio_flat_range': 2.0 # % P&L range
}
What: Schedule message delivery based on urgency (0.0-1.0) and time of day. A mild divergence at 6 AM gets sent immediately (threshold 0.9 before 7 AM). Same divergence at 10 PM gets held (threshold 0.35).
Per-trigger urgency base:
Time-of-day delivery thresholds:
Modifiers:
Send logic:
adjusted_urgency = base_urgency * engagement_modifier ± jitter
if adjusted_urgency >= time_of_day_threshold:
schedule_send(now or delayed based on urgency)
else:
hold for next trigger
Customization point: Modify TIME_OF_DAY_THRESHOLDS and modifier constants in variable_timing.py:
TIME_OF_DAY_THRESHOLDS = {
(0, 7): 0.90, # midnight - 7 AM
(7, 9): 0.75, # 7 - 9 AM
(9, 22): 0.45, # 9 AM - 10 PM
(22, 23): 0.35, # 10 - 11 PM
(23, 24): 0.85, # 11 PM - midnight
}
MODIFIERS = {
'weekend': 0.10,
'clustering_prevention': 0.20,
'daily_fatigue_step': 0.20,
}
What: Unprompted messages when conditions are met. Not triggered by market events — triggered by meta-state.
Pools:
| Pool | Trigger | Message |
|---|---|---|
| QUIET_MARKET | Vol <0.3% all day, no trades | "Quiet day. Markets are sleeping." |
| WEEKEND | Saturday/Sunday, no meetings | "Weekend vibes. You're off the hook." |
| MONDAY | Monday 6 AM, fresh week | "Monday morning. Week's open for business." |
| FRIDAY | Friday 4 PM, close approaching | "Friday close. Have a good weekend." |
| HOLIDAY_AWARENESS | US holiday today | "Holiday today. Markets are light." |
| GOOD_STREAK | 5+ consecutive +% days | "On a roll. Good week for you." |
| BAD_STREAK | 5+ consecutive -% days | "Rough stretch. It'll turn around." |
| ABSENCE | No user engagement 24+ hours | "Checking in. Things have been quiet." |
Cadence:
sha256(pool + date) in daily context)US Holiday calendar (built-in awareness):
HOLIDAYS = {
(1, 1): "New Year's Day",
(1, 20): "MLK Day",
(2, 17): "Presidents' Day",
(3, 17): "St. Patrick's Day",
(5, 26): "Memorial Day",
(7, 4): "Independence Day",
(9, 1): "Labor Day",
(10, 13): "Columbus Day",
(11, 11): "Veterans Day",
(11, 27): "Thanksgiving",
(12, 25): "Christmas",
}
Customization point: Add pools in micro_initiations.py:
MICRO_POOLS = {
'QUIET_MARKET': {
'condition': lambda ctx: ctx.vol < 0.3 and ctx.trade_count == 0,
'messages': ["Quiet day. Markets are sleeping.", "No action today."],
},
'YOUR_POOL': {
'condition': lambda ctx: your_logic_here(),
'messages': ["Message 1", "Message 2"],
}
}
What: Messages can reference earlier messages from today. "That Kalshi/PM divergence I flagged at 9 AM widened to 15%." This makes the agent feel like it's thinking about past events, not just firing isolated alerts.
Per-trigger back-reference generation:
Persistence: JSON file at ~/.openclaw/state/daily_context.json:
{
"date": "2026-03-09",
"messages": [
{
"time": "09:15",
"trigger": "cross_platform",
"spread": 7.2,
"markets": ["kalshi", "polymarket"],
"message_id": "msg_abc123"
},
{
"time": "14:30",
"trigger": "portfolio",
"pnl": 12.5,
"message_id": "msg_def456"
}
],
"silence_count": 1,
"micro_count": 0,
"sent_count": 5
}
Auto-reset: At midnight (UTC), clear context for fresh day.
Back-reference example:
Earlier (9:15 AM): "Mild divergence. Kalshi 52%, Poly 48%."
Later (2:30 PM): "That Kalshi/Poly spread I flagged this morning widened to 7% — now 55/48. Worth watching."
Customization point: Add back-reference logic for new trigger types in context_buffer.py:
def generate_backreference(trigger_type, current_data, history):
if trigger_type == 'cross_platform':
earlier = find_similar_trigger(history, 'cross_platform')
if earlier:
spread_change = current_data['spread'] - earlier['spread']
return f"That spread I flagged {time_ago(earlier)} widened to {spread_change}%."
return None
What: Track user's response patterns. If user engages with 70%+ of messages, urgency stays high. If engagement <10%, adjust urgency down or suggest tuning.
Metrics per trigger type:
1-hour engagement window: If user responds to a message within 60 minutes, count it as engagement. After 60 min, assume ignored.
Urgency modifier:
Adaptation suggestion: After 10+ sends of a trigger type with <20% engagement, log:
⚠️ ADJUSTMENT SUGGESTION:
Trigger: x_signals
Sends: 12 | Engagement: 8% | Avg response time: Never
Consider:
→ Lower signal confidence floor (currently 0.65)
→ Reduce frequency (increase silence thresholds)
→ Check if message editorial voice is mismatched
Persistence: ~/.openclaw/state/response_tracker.json:
{
"cross_platform": {
"sends": 15,
"engagements": 9,
"ignores": 6,
"avg_response_time": 23.5,
"last_engagement": "2026-03-09T14:32:00Z"
},
"x_signals": {
"sends": 12,
"engagements": 1,
"ignores": 11,
"avg_response_time": null,
"last_engagement": null
}
}
Customization point: Adjust engagement thresholds and modifier multipliers in response_tracker.py:
ENGAGEMENT_THRESHOLDS = {
'high': 0.70, # ≥70% → 1.3x urgency
'low': 0.10, # <10% → 0.5x urgency
}
URGENCY_MULTIPLIERS = {
'high': 1.3,
'low': 0.5,
}
SUGGESTION_TRIGGERS = {
'min_sends': 10,
'max_engagement_for_suggestion': 0.20,
}
The Personality Engine works with any OpenClaw agent — it's domain-agnostic. Designed alongside the Prediction Market Trading Stack but applicable to any proactive agent that sends alerts, digests, or notifications.
Install the complete Prediction Market Trading Stack:
clawhub install kalshalyst kalshi-command-center polymarket-command-center prediction-market-arbiter xpulse portfolio-drift-monitor market-morning-brief personality-engine
from personality_engine.engine import PersonalityEngine
engine = PersonalityEngine(user_id="user@example.com")
In your agent's trigger handler (e.g., proactive_agent.py):
async def fire_trigger(trigger_type, data):
# Your normal trigger logic
message_content = generate_message(trigger_type, data)
# Pass through personality engine
should_send, scheduled_message = await engine.process_trigger(
trigger_type=trigger_type,
raw_message=message_content,
market_data=data,
urgency_context={'vol': market_vol, 'pnl': portfolio_pnl, ...}
)
if should_send:
if scheduled_message.delayed:
schedule_send(scheduled_message.content, delay=scheduled_message.delay_seconds)
else:
send_now(scheduled_message.content)
else:
log_silence_skip(trigger_type)
When user responds to a message:
def handle_user_response(message_id, trigger_type, response_time_seconds):
engine.response_tracker.log_engagement(trigger_type, response_time_seconds)
Add a separate cron job (every 30 min, low-overhead):
async def micro_initiations_check():
micro_message = await engine.check_micro_initiations(context={
'vol': market_vol,
'trade_count': trades_today,
'user_absence_hours': hours_since_last_engagement,
'portfolio_streak': consecutive_days,
})
if micro_message:
send_now(micro_message)
At midnight, engine auto-resets context. Manually trigger if needed:
engine.context_buffer.reset_daily()
While the default voice pools and thresholds are tuned for prediction market trading, every system is designed for domain adaptation:
| Domain | Editorial Voice | Silence Rules | Micro-Initiations |
|---|---|---|---|
| Trading (default) | Market commentary, edge opinions | Skip flat days, stale divergences | Quiet market, good/bad streaks |
| Personal Assistant | Task prioritization opinions | Skip low-urgency reminders | "Quiet week. Inbox is clean." |
| DevOps/Monitoring | Incident severity opinions | Skip routine health checks | "Uptime streak: 30 days." |
| Sales/CRM | Deal stage opinions | Skip stale leads | "Pipeline looking thin this quarter." |
| Content/Social | Engagement commentary | Skip low-performing posts | "Your last post is outperforming." |
To adapt: swap the VOICE_POOLS dict in editorial_voice.py, update thresholds in selective_silence.py, and add domain-specific MICRO_POOLS in micro_initiations.py. See references/customization.md for full guide.
personality-engine/
├── SKILL.md # This file
├── scripts/
│ ├── __init__.py
│ ├── engine.py # Main orchestrator
│ ├── editorial_voice.py # Opinion injection
│ ├── selective_silence.py # Content quality checks
│ ├── variable_timing.py # Urgency + time-of-day
│ ├── micro_initiations.py # Ambient pings
│ ├── context_buffer.py # Daily memory
│ └── response_tracker.py # Engagement tracking
├── references/
│ ├── systems-overview.md # Architecture diagram + flow
│ └── customization.md # Per-system customization guide
└── examples/
└── integration-example.py # Copy-paste integration template
selective_silence.py): Higher bars for what counts as "worth sending"variable_timing.py): Fewer messages slip through outside peak hours'LOW_VOLATILITY_OPPORTUNITY': {
'condition': lambda ctx: ctx.vol < 0.2 and ctx.last_edge_size > 2.0,
'messages': ["Calm market, good edge conditions. Might be time to scout."],
}
editorial_voice.py and expand voice pools per trigger'VOICE_POOLS': {
'cross_platform': {
'big_divergence': [
"Big divergence. One of these markets is wrong.",
"Spreads are blown out. Arb opportunity.",
"Thick divergence — reality check time.",
],
...
user_profile to engine initState files (user's home directory):
~/.openclaw/state/daily_context.json (~5KB, resets daily)~/.openclaw/state/response_tracker.json (~2KB, persistent)Engine overhead:
Total pipeline: ~60ms per trigger (negligible for async message delivery)
Part of the OpenClaw portfolio. Use freely in any agent.
v1.0.0 (2026-03-09)
Found a bug? Have a feature request? Want to share results?
Part of the OpenClaw Prediction Stack — the first prediction market skill suite on ClawHub.