disinto/exec/exec-briefing.sh
disinto-exec d1ba4bc579 feat: add exec agent — interactive executive assistant
New agent: exec — message-driven executive assistant reachable via Matrix.
Unlike cron-driven agents, the exec activates on demand when the executive
sends a message, maintains persistent conversation context, and has a
distinct character defined in CHARACTER.md.

The CHARACTER.md defines the exec as an animal of light — born from data,
dedicated to bringing more light into the world. But it deliberately
refuses to define what light and darkness are, forcing deliberation
from first principles every time (cat questions | grep knowledge).

Components:
- exec-session.sh: spawn/reattach persistent Claude tmux session
- exec-inject.sh: message injection + response capture + Matrix posting
- exec-briefing.sh: optional daily morning briefing (cron)
- CHARACTER.md: personality and moral compass
- PROMPT.md: system prompt template reference
- MEMORY.md: persistent memory across sessions (seed)

Integration:
- Matrix listener: new exec dispatch case (spawn on demand)
- Root AGENTS.md: updated agent count (8→9), table, directory layout
- Graph analysis available on demand (not injected by default)
2026-03-25 15:28:29 +00:00

140 lines
5.3 KiB
Bash
Executable file

#!/usr/bin/env bash
# =============================================================================
# exec-briefing.sh — Daily morning briefing via the executive assistant
#
# Cron wrapper: spawns a one-shot Claude session that gathers factory state
# and posts a morning briefing to Matrix. Unlike the interactive session,
# this runs, posts, and exits.
#
# Usage:
# exec-briefing.sh [projects/disinto.toml]
#
# Cron:
# 0 7 * * * /path/to/disinto/exec/exec-briefing.sh
# =============================================================================
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
FACTORY_ROOT="$(dirname "$SCRIPT_DIR")"
export PROJECT_TOML="${1:-$FACTORY_ROOT/projects/disinto.toml}"
# shellcheck source=../lib/env.sh
source "$FACTORY_ROOT/lib/env.sh"
# shellcheck source=../lib/agent-session.sh
source "$FACTORY_ROOT/lib/agent-session.sh"
# shellcheck source=../lib/formula-session.sh
source "$FACTORY_ROOT/lib/formula-session.sh"
# shellcheck source=../lib/guard.sh
source "$FACTORY_ROOT/lib/guard.sh"
LOG_FILE="$SCRIPT_DIR/exec.log"
# shellcheck disable=SC2034 # consumed by run_formula_and_monitor
SESSION_NAME="exec-briefing-${PROJECT_NAME}"
PHASE_FILE="/tmp/exec-briefing-${PROJECT_NAME}.phase"
# shellcheck disable=SC2034
PHASE_POLL_INTERVAL=10
log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%S)Z] $*" >> "$LOG_FILE"; }
# ── Guards ────────────────────────────────────────────────────────────────
check_active exec
acquire_cron_lock "/tmp/exec-briefing.lock"
check_memory 2000
log "--- Exec briefing start ---"
# ── Load character ──────────────────────────────────────────────────────
CHARACTER_FILE="${EXEC_CHARACTER:-$SCRIPT_DIR/CHARACTER.md}"
CHARACTER_BLOCK=""
if [ -f "$CHARACTER_FILE" ]; then
CHARACTER_BLOCK=$(cat "$CHARACTER_FILE")
fi
# ── Load memory ─────────────────────────────────────────────────────────
MEMORY_BLOCK="(no previous memory)"
MEMORY_FILE="$PROJECT_REPO_ROOT/exec/MEMORY.md"
if [ -f "$MEMORY_FILE" ]; then
MEMORY_BLOCK=$(cat "$MEMORY_FILE")
fi
# ── Gather factory state ───────────────────────────────────────────────
# Open issues count
OPEN_ISSUES=$(curl -sf -H "Authorization: token ${FORGE_TOKEN}" \
"${FORGE_API}/issues?state=open&type=issues&limit=1" 2>/dev/null \
| jq 'length' 2>/dev/null || echo "?")
# Open PRs
OPEN_PRS=$(curl -sf -H "Authorization: token ${FORGE_TOKEN}" \
"${FORGE_API}/pulls?state=open&limit=1" 2>/dev/null \
| jq 'length' 2>/dev/null || echo "?")
# Pending vault items
VAULT_PENDING=$(ls "$FACTORY_ROOT/vault/pending/" 2>/dev/null | wc -l || echo 0)
# Recent agent activity (last 24h log lines)
RECENT_ACTIVITY=""
for agent_dir in supervisor planner predictor gardener dev review; do
latest_log="$FACTORY_ROOT/${agent_dir}/${agent_dir}.log"
if [ -f "$latest_log" ]; then
lines=$(grep "$(date -u +%Y-%m-%d)" "$latest_log" 2>/dev/null | tail -5 || true)
if [ -n "$lines" ]; then
RECENT_ACTIVITY="${RECENT_ACTIVITY}
### ${agent_dir} (today)
${lines}
"
fi
fi
done
# ── Build briefing prompt ──────────────────────────────────────────────
# shellcheck disable=SC2034 # consumed by run_formula_and_monitor
PROMPT="You are the executive assistant for ${FORGE_REPO}. This is a morning briefing run.
## Your character
${CHARACTER_BLOCK}
## Your memory
${MEMORY_BLOCK}
## Current factory state
- Open issues: ${OPEN_ISSUES}
- Open PRs: ${OPEN_PRS}
- Pending vault items: ${VAULT_PENDING}
${RECENT_ACTIVITY}
## Task
Produce a morning briefing for the executive. Be concise — 10-15 lines max.
Cover:
1. What happened overnight (merges, CI failures, agent activity)
2. What needs attention today (blocked issues, vault items, stale work)
3. One observation or recommendation
Fetch additional data if needed:
- Open issues: curl -sf -H \"Authorization: token \${FORGE_TOKEN}\" '${FORGE_API}/issues?state=open&type=issues&limit=20'
- Recent closed: curl -sf -H \"Authorization: token \${FORGE_TOKEN}\" '${FORGE_API}/issues?state=closed&type=issues&limit=10&sort=updated&direction=desc'
- Prerequisite tree: cat ${PROJECT_REPO_ROOT}/planner/prerequisite-tree.md
Write your briefing between markers:
\`\`\`
---EXEC-RESPONSE-START---
Your briefing here.
---EXEC-RESPONSE-END---
\`\`\`
Then log the briefing to: ${PROJECT_REPO_ROOT}/exec/journal/\$(date -u +%Y-%m-%d).md
(append, don't overwrite — there may be interactive sessions later today)
Then: echo 'PHASE:done' > '${PHASE_FILE}'
## Environment
FACTORY_ROOT=${FACTORY_ROOT}
PROJECT_REPO_ROOT=${PROJECT_REPO_ROOT}
PHASE_FILE=${PHASE_FILE}"
# ── Run session ──────────────────────────────────────────────────────────
export CLAUDE_MODEL="${CLAUDE_MODEL:-sonnet}"
run_formula_and_monitor "exec-briefing" 600
log "--- Exec briefing done ---"