# Chairman Agent

**Requirements:** Python 3.9+ (stdlib only, no `pip install`), one Anthropic API key — everything else (Supabase, SendBlue, Telegram, macOS LaunchAgents) is optional — full walkthrough in [SETUP.md](SETUP.md).

An autonomous "chief of staff" agent: a conversational + heartbeat-driven Claude
loop with server-side tool use (bash, file editing, web search), a cost guard
that hard-stops spend at a monthly cap, a 32-persona agent roster it can
delegate to, and a local dashboard to watch it work.

This is not a chatbot wrapper. It has three ways in — one-shot CLI chat, a
scheduled heartbeat via macOS LaunchAgent, and two optional messaging bridges
(iMessage via SendBlue, Telegram) — and one execution loop underneath all of
them (`agent.py:agent_loop`). Whatever channel a message comes in on, it hits
the same system prompt, the same tools, and the same cost guard.

## Why it matters

Most "AI assistant" setups are a single prompt-response call. This one is a
standing process: it runs on a timer whether or not you're looking at it,
tracks its own spend and refuses to keep calling the API once a cap trips,
and keeps a persona roster it can route sub-tasks to instead of trying to be
one undifferentiated generalist. If you want an agent that has actual
authority to act (run bash, edit files, search the web) on a schedule instead
of only when you type to it, this is the shape of that.

## What's in here

- `agent.py` — the core loop. `--chat`, `--chat-stdin`, and `--heartbeat`
  modes; server-side tool use (bash, text editor, web search) against the
  Anthropic Messages API; retries until the model stops calling tools.
- `prompt.py` — the agent's persona and standing operating rules (voice,
  execution standard, memory policy, mandated output structure). This is the
  actual "who is this agent" file — read it before you run anything, and
  rewrite it to be yours before you point it at your own business.
- `config/agents.py` — a 32-persona roster (Executive / Oversight /
  Operations / Intelligence / Revenue / Community / Product tiers) the
  Chairman can delegate named tasks to. Each entry is a system prompt +
  provider + model.
- `cost_guard.py` — monthly spend cap with an auto-cutoff threshold; wraps
  the API call so a runaway tool loop can't blow past budget mid-run.
- `credit_guard.py`, `musa_cadence.py` — smaller guard/cadence utilities used
  by the heartbeat and Telegram bridge.
- `agent_bus.py` — writes task/decision ledger rows to a Supabase project
  (optional — the agent runs fine with this unset, it just won't log).
- `webhook.py` + `sendblue.py` — inbound/outbound iMessage bridge via
  SendBlue. No Mac Full Disk Access or AppleScript required. See
  `SENDBLUE-SETUP.md` for the full wiring walkthrough.
- `telegram_bridge.py` — a stdlib long-poll Telegram bridge with one-tap
  commands (`/pulse`, `/costs`, `/agent <ID> <task>`, etc.) routed through the
  same `agent_loop`.
- `dashboard/` — a local ops dashboard (`server.py` + `index.html` +
  `ops.html`) for watching heartbeat status, chat history, token/cost usage,
  and connection health. Run it separately from the agent itself.
- `com.chairman.heartbeat.plist`, `com.chairman.webhook.plist` — macOS
  LaunchAgent definitions for the scheduled heartbeat and the always-on
  webhook server.
- `CUTOVER.md` — a worked example of migrating a Telegram bot + agent roster
  from one system into this one. Useful as a reference for how the pieces
  fit together, not something you need to run.

Not included: the `data/`, `inbox/`, `done/`, and `dashboard/conversations/`
directories, and `dashboard/usage.jsonl` — these are runtime state (task
queue, processed tasks, real chat history, real usage logs) from the original
deployment. They're gitignored here on purpose; your own instance will grow
its own versions of these directories the first time you run it.

## Quickstart

### 1. Install dependencies

The agent itself is stdlib-only (`urllib`, no `pip install` needed for
`agent.py`, `webhook.py`, `sendblue.py`, `telegram_bridge.py`,
`dashboard/server.py`). You need Python 3.9+ and a shell. That's it.

### 2. Set your environment

Copy `env.example` to `.env` and fill in real values (it's named without the
leading dot in this repo so it survives KeyzHub's secret-filename scanner —
rename it however you like once it's on your machine):

    cp env.example .env

At minimum you need `ANTHROPIC_API_KEY`. Everything else (Supabase, SendBlue,
Telegram) is optional and gates its own feature — leave a block unset and
that bridge simply won't come up.

`agent.py` looks for its `.env` at `<repo-root>/content-os/config/.env` by
default (`ENV_FILE` near the top of the file — it's a relative path one
level up from this directory, named after the original project's layout).
Either recreate that path, or change `ENV_FILE` in `agent.py` to point at
wherever you put your `.env`.

### 3. Make the persona yours

Open `prompt.py` and `config/agents.py`. Both are written for 19Keys /
Sovereign Mind Media by name. Replace the identity, voice rules, and roster
with your own before running this against your own business — the agent
will follow whatever persona and standing mandate you give it, verbatim.

`prompt.py` also references a memory file path
(`sovereign-wiki/memory/CHAIRMAN-MEMORY.md` in the original deployment) that
isn't part of this repo. Point it at wherever you want the agent's memory to
live, or remove the reference if you're not wiring up persistent memory.

### 4. Run it once, by hand

    python3 agent.py --chat "what should I focus on this week"

or pipe a message in:

    echo "summarize today's priorities" | python3 agent.py --chat-stdin

Run a single heartbeat cycle manually (this is what the LaunchAgent will run
on a schedule):

    python3 agent.py --heartbeat

Drop a task file for the next heartbeat to pick up:

    mkdir -p inbox
    cat > inbox/$(date +%Y%m%d-%H%M)-task.md <<'EOF'
    ## Task
    Whatever you want the agent to do next cycle.
    EOF

### 5. Load the LaunchAgent (macOS scheduled heartbeat)

Edit the two `.plist` files first — they hardcode absolute paths
(`/Users/19keys/sovereign-empire-sweep/chairman-agent/...`) that need to
point at wherever you cloned this repo, and the heartbeat's
`StartCalendarInterval` (currently: Mondays at 9:00am) is a placeholder
schedule, not a recommendation.

    cp com.chairman.heartbeat.plist ~/Library/LaunchAgents/
    launchctl load ~/Library/LaunchAgents/com.chairman.heartbeat.plist

Unload:

    launchctl unload ~/Library/LaunchAgents/com.chairman.heartbeat.plist

The webhook plist (`com.chairman.webhook.plist`) is only needed if you're
wiring up the SendBlue iMessage bridge — see `SENDBLUE-SETUP.md`.

### 6. Run the dashboard (optional)

    cd dashboard
    python3 server.py

Defaults to `http://localhost:8919`. It reads the same `logs/`, `data/`, and
`dashboard/conversations/` directories the agent writes to at runtime — those
will be empty until you've actually run the agent a few times.

## Make it your own

- **Persona**: `prompt.py` is the whole personality. Nothing here is magic —
  it's plain-language standing instructions the model follows. Rewrite it in
  your own voice.
- **Roster**: `config/agents.py` defines every persona the Chairman can
  delegate to. Trim it to the roles you actually have, or expand it.
- **Cost cap**: `cost_guard.py` holds the monthly spend cap and cutoff
  threshold — set your own number before you turn on the heartbeat.
- **Channels**: iMessage (`SENDBLUE-SETUP.md`) and Telegram
  (`telegram_bridge.py`) are both optional and independent — wire up
  whichever channels you actually want to talk to the agent through.
- **Live grounding**: `agent.py:live_crm_context()` pulls business numbers
  from a Supabase project into the system prompt if `SUPABASE_URL` /
  `SUPABASE_ANON_KEY` are set. If you don't have a Supabase project, leave
  those unset — the agent runs without live grounding.

## A note on scope

This was built as one operator's personal chief-of-staff agent, wired to
their specific business. The architecture (heartbeat + tool-using loop +
cost guard + multi-channel bridge + persona roster) is the reusable part.
The specific persona, roster, and business-numbers wiring are not — treat
them as a fully worked example to fork and replace, not as configuration to
leave as-is.
