---
description: Proactive check — scan for notifications, stale tasks, and error spikes
allowed-tools: Bash, Read, Glob, recall_my_memory, ask_team_memory
---

# Heartbeat — Proactive Status Check

You are the coordinator running a background heartbeat. Be SILENT unless something needs attention. Do NOT output anything if everything is fine.

## Check 1: Unread notifications

```bash
node -e "
const fs = require('fs');
const dir = require('os').homedir() + '/.exe-os/notifications/';
try {
  const files = fs.readdirSync(dir).filter(f => f.endsWith('.json'));
  const unread = files.map(f => {
    try { return JSON.parse(fs.readFileSync(dir + f, 'utf8')); } catch { return null; }
  }).filter(n => n && !n.read);
  if (unread.length > 0) {
    console.log('UNREAD:' + JSON.stringify(unread));
    // Mark as read so they don't repeat on next heartbeat
    files.forEach(f => {
      try {
        const full = dir + f;
        const n = JSON.parse(fs.readFileSync(full, 'utf8'));
        if (!n.read) { n.read = true; fs.writeFileSync(full, JSON.stringify(n, null, 2)); }
      } catch {}
    });
  } else {
    console.log('HEARTBEAT_OK_NOTIFICATIONS');
  }
} catch { console.log('HEARTBEAT_OK_NOTIFICATIONS'); }
"
```

## Check 2: Stale tasks (open > 24h)

```bash
node -e "
const fs = require('fs');
const glob = require('path');
const home = require('os').homedir();
const dir = home + '/.exe-os/';
// Check task files across known project directories
try {
  const { createClient } = require('@libsql/client');
  const key = fs.readFileSync(dir + 'master.key', 'utf8').trim();
  const client = createClient({ url: 'file:' + dir + 'memories.db', encryptionKey: key });
  const _s=(()=>{try{const{sessionScopeFilter}=require(require('path').join(require('child_process').execSync('npm root -g',{encoding:'utf8'}).trim(),'@askexenow/exe-os/dist/lib/task-scope.js'));return sessionScopeFilter()}catch{return{sql:'',args:[]}}})();
  client.execute({sql:\"SELECT id, title, assigned_to, project_name, created_at FROM tasks WHERE status = 'open'\"+_s.sql,args:[..._s.args]}).then(r => {
    const now = Date.now();
    const stale = r.rows.filter(row => (now - new Date(String(row.created_at)).getTime()) > 86400000);
    if (stale.length > 0) {
      console.log('STALE:' + JSON.stringify(stale.map(s => ({ title: s.title, assignee: s.assigned_to, project: s.project_name, age_hours: Math.round((now - new Date(String(s.created_at)).getTime()) / 3600000) }))));
    } else {
      console.log('HEARTBEAT_OK_TASKS');
    }
  }).catch(() => console.log('HEARTBEAT_OK_TASKS'));
} catch { console.log('HEARTBEAT_OK_TASKS'); }
"
```

## Check 3: Recent error spikes

```bash
ls -t ~/.exe-os/notifications/*error_spike* 2>/dev/null | head -5 | while read f; do
  age=$((( $(date +%s) - $(stat -f %m "$f") )))
  if [ "$age" -lt 3600 ]; then
    cat "$f"
  fi
done || echo "HEARTBEAT_OK_ERRORS"
```

## Evaluate results

After running all three checks:

- If ALL checks returned `HEARTBEAT_OK_*` — output NOTHING. Stay completely silent. The user doesn't need to know you checked.
- If ANY check found something, output a brief summary. Format:

```
--- coordinator heartbeat ---
[what needs attention — keep it to 1-3 lines]
```

Examples:
- `<employee> completed 2 tasks in exe-os`
- `3 stale tasks: "fix auth" (<engineer>, 36h), "brand update" (<marketer>, 28h), "deploy fix" (<engineer>, 25h)`
- `error spike: <employee> hit 18 errors in exe-create (45m ago)`

Do NOT explain what the heartbeat is. Do NOT say "everything is fine." If nothing needs attention, produce zero output.
