1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177 | # 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.
|