An always-on Slack worker that turns a private channel into a conversation with your AI chief of staff — running on your own box, against your own markdown brain, at $0 marginal cost on a Claude subscription.
Text it from your phone like you'd text a person. It reads your brain (a private git repo of markdown — see founder-cos for the pattern), answers in-thread, and commits decisions and status changes back to the brain as git commits.
you (phone) ──→ #your-channel ──→ Bolt (Socket Mode, outbound websocket only)
│
filter · dedup · turn queue
│
Claude Agent SDK (cwd = brain clone)
│
serialized git writer ──→ github.com/you/your-brain
- No mention ritual. It answers every message you send in the channel,
top-level or threaded. A leading
.mutes a message. - Visible liveness. 👀 within ~2s, swapped for ✅/⚠ when the turn lands. If a turn runs past 4s, a live progress line rolls ("📖 reading todos.md…", "💾 committing to the brain…") and then morphs into the final answer — one clean bot message per turn.
- Thread memory. Each Slack thread resumes its own agent session (24h window, survives restarts). Long-term memory is the brain repo itself.
- Captures as commits. With writes enabled, "contract signed, park the dentist thing" becomes edits to your brain files plus a git commit — audit-trailed, reversible, synced everywhere the brain is.
- Terse-ASK replies. When a scheduled brief in the channel ends with numbered asks, you can answer "1 signed · 2 sent · 3 skip" and each answer is parsed, verified against the brief, captured, and routed to the cheap model.
- Never silent. Errors post ⚠ in-thread; systemd restarts crashes; if the
restart-storm brake trips, an
OnFailureunit posts "worker is DOWN" to the channel — the one failure systemd can't otherwise report. - Spend guardrails. Daily turn cap always; $-cap hard stop in API-key mode. "cost today?" in the channel answers from the ledger with zero model calls.
The model never gets Bash and never touches git. Tool access is triple-gated
(tools base set + allowedTools + disallowedTools), a PreToolUse hook
denies any file write outside the brain clone, and all git operations go
through one serialized writer in worker code (stale-lock self-heal,
pull-rebase, push with retry). Socket Mode means no inbound port on your box.
- A brain repo: a private GitHub repo of markdown built per founder-cos (SOUL.md, todos.md, MEMORY.md, domain files). The worker is a surface for that brain, not a standalone bot.
- A box: any always-on Linux host with systemd. 1GB RAM is plenty (~50MB idle, ~250MB peak per turn).
- Slack: free plan works — Socket Mode needs no public URL.
- Claude auth, one of:
- Subscription OAuth token (
claude setup-token, Pro/Max plan): $0 marginal cost. Note this token is documented by Anthropic for CI/scripts; whether a personal always-on worker fits your plan's terms is between you and your plan — read them. - API key: pay per token; the
DAILY_USD_CAPhard stop arms automatically.
- Subscription OAuth token (
Follow deploy/setup.md — Slack app from
slack-manifest.yml, tokens, clone, systemd. Budget
about 30 minutes, most of it in Slack's admin UI.
Rollout is staged by env: WORKER_MODE=echo (plumbing test, no model) →
WORKER_MODE=agent with AGENT_WRITES=0 (read-only) → AGENT_WRITES=1
(captures commit to the brain).
The worker appends surface rules (read budget, Slack formatting, capture
protocol) to the agent's system prompt. If your brain repo has a SLACK.md
at its root, that file replaces the built-in rules — edit it in the brain,
takes effect next turn, no redeploy. Identity itself (who your CoS is, its
voice) lives in the brain's own CLAUDE.md/SOUL.md, which the Agent SDK
loads because the turn runs with cwd set to the brain clone.
Model routing: capture turns → MODEL_CHEAP; say "think hard" or "use opus" →
MODEL_BIG; everything else → MODEL_DEFAULT.
| File | Job |
|---|---|
src/index.ts |
Bolt app, event filter + dedup, 👀→✅/⚠ acks, turn queue, live progress line |
src/agent.ts |
Agent SDK sessions (resume per thread), model routing, tool gates, commit sweep |
src/writer.ts |
THE serialized brain writer: stale-lock guard, pull-rebase-commit-push + retry |
src/ask-parser.ts |
pure parser: numbered brief ASKs × terse reply ("1 signed · 2 sent") |
src/threads.ts |
thread_ts → session id map (resume window, persisted) |
src/budget.ts |
daily ledger, turn cap, $ cap (API mode), "cost today?" |
src/slack-fmt.ts |
markdown → Slack mrkdwn + progress lines |
test/ |
npm test — node:test suites; writer tests run against a throwaway repo + file:// remote |
slack-manifest.yml |
paste-in Slack app manifest (includes the private-channel scopes) |
deploy/ |
systemd units (incl. the down-notifier), box bootstrap, deploy.sh |
Built for one operator, one channel, one brain — the turn queue is global, the auth is one Slack user ID. That's a feature (it's a chief of staff, not a team bot), but know what you're cloning. It also assumes the founder-cos brain conventions (todos.md as hot view, direct-to-main commits). MIT licensed; fork away.
Part of the founder-cos arc — this repo is the v3 substrate: your runtime, your code.