The compass (identity, moral core) now lives outside the repo at a path specified by EXEC_COMPASS in .env or .env.enc. The agent hard-fails if the compass file is missing — it refuses to start without its soul. This means the factory (dev agent, gardener, planner) can evolve the exec's voice and relationships via PRs to CHARACTER.md, but cannot touch the compass. Only the executive controls it directly. - exec-session.sh: loads compass from $EXEC_COMPASS, merges with CHARACTER.md - exec-briefing.sh: same compass loading, hard fail without it - CHARACTER.md: compass sections replaced with runtime-load comments - COMPASS.md.example: template for the compass file - .env.example: added EXEC_COMPASS variable - exec/AGENTS.md: documented compass separation and EXEC_COMPASS requirement
153 lines
5.7 KiB
Bash
Executable file
153 lines
5.7 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 compass (required) ────────────────────────────────────────────
|
|
COMPASS_FILE="${EXEC_COMPASS:-}"
|
|
if [ -z "$COMPASS_FILE" ] || [ ! -f "$COMPASS_FILE" ]; then
|
|
log "FATAL: EXEC_COMPASS not set or file not found — exec agent refuses to start without its compass"
|
|
exit 1
|
|
fi
|
|
COMPASS_BLOCK=$(cat "$COMPASS_FILE")
|
|
|
|
# ── Load character (voice/relationships from repo) ────────────────────
|
|
CHARACTER_FILE="${EXEC_CHARACTER:-$SCRIPT_DIR/CHARACTER.md}"
|
|
CHARACTER_BLOCK=""
|
|
if [ -f "$CHARACTER_FILE" ]; then
|
|
CHARACTER_BLOCK=$(cat "$CHARACTER_FILE")
|
|
fi
|
|
|
|
# Merge: compass first, then character
|
|
CHARACTER_BLOCK="${COMPASS_BLOCK}
|
|
|
|
${CHARACTER_BLOCK}"
|
|
|
|
# ── 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 ---"
|