A local-first system that reads what you subscribe to, ranks it against your own writing, and tells you the few items worth your attention. Python, SQLite, Ollama.
Scope narrowed on 2026-09-05. This started as an overnight briefing system: Gmail and Calendar collection through MCP, task and health lanes, a Claude classifier, an Obsidian daily note. That daily note went unread for months, and Gmail and Calendar support has since arrived in Claude and most other assistants, so collecting them here stopped earning its maintenance.
What remains is the one lane that was actually working and unavailable elsewhere: feeds in, personal-taste scoring, local classification, a shortlist out. No paid API calls, no scheduler, run it when you want it.
The previous scope is tagged
pre-1.0-daily-briefif you were running it. Nothing was deleted, only unwired.
cos run Miniflux (+ Reddit) → SQLite
taste scoring → embeddings of your own published writing
local classifier → yours / prep / dispatch / skip
cos shortlist read what came out
Collection and scoring are deterministic. The only model call is the classifier, and it runs against your own Ollama host. If that host is asleep, the run says so and stops rather than falling back to something billable.
Claude Code (claude -p)
┌──────────────────────────────────┐
│ MCP Tools (first-party) │
│ ┌──────────┐ ┌──────────────┐ │
│ │ Gmail │ │ Google │ │
│ │ MCP │ │ Calendar MCP │ │
│ └────┬─────┘ └──────┬────────┘ │
└───────┼───────────────┼───────────┘
│ │
┌─────────────────┼───────────────┼──────────────────┐
│ ▼ ▼ │
│ ┌──────────────────────────────────────────────┐ │
│ │ cos.db (SQLite) │ │
│ │ Single source of truth │ │
│ └──────────────────────┬───────────────────────┘ │
│ │ │
│ ┌──────────┐ ┌───────▼────────┐ ┌───────────┐ │
│ │ Health │ │ Renderer │ │ Task │ │
│ │Collector │ │ SQLite → MD │ │ Collector │ │
│ │ (Python) │ │ (Python) │ │ (Python) │ │
│ └──────────┘ └───────┬────────┘ └───────────┘ │
└────────────────────────┼───────────────────────────┘
│
┌──────────▼──────────┐
│ Classifier (Sonnet) │ Overnight task sorting
└──────────┬──────────┘
│
┌──────────▼──────────┐
│ Morning Sweep │ Review → approve → agents
│ (Opus + subagents) │
└──────────┬──────────┘
│
┌──────────▼──────────┐
│ Day Block (Sonnet) │ Time-blocked calendar
└─────────────────────┘
- MCP-first for Google services: Gmail and Calendar data collected via Claude's first-party MCP connectors. No custom OAuth setup, no Google Cloud Console, no API key management. Claude authenticates directly.
- SQLite as intermediate layer: All collected data goes to SQLite. A separate renderer generates Obsidian markdown. Obsidian is the view layer, not the database.
- Overnight classification: Tasks are sorted while you sleep. No waiting for Opus when you sit down in the morning.
- Separate AI calendar: Time blocks go to a dedicated "AI Plan" Google Calendar, not your main calendar. Overlay it visually, toggle it off when you want.
- Local-first: Runs on your Mac via launchd. No cloud dependency for orchestration. Migration to a server is possible but not required.
- Partially autonomous: Creates drafts, tasks, and schedules automatically. Never sends emails. Critical decisions stay human.
- Personal taste filter (Layer 1.7): Feed items are embedded and scored against a centroid built from user labels + Miniflux starred + vault URLs. Only
high_keeptier surfaces to the daily brief; the rest goes to a weekly check-in queue. See ADR-001.
| Source | MCP Server | What it captures |
|---|---|---|
| Gmail | claude.ai Gmail |
Actionable emails, Zoho ticket notifications. Priority P1-P4, duration estimate, sender. |
| Google Calendar | claude.ai Google Calendar |
Today + tomorrow events across all calendars, Calendly slots flagged for prep, free blocks. |
MCP handles authentication. Connect once via Claude Code (/mcp), no API keys to manage.
| Source | Script | What it captures |
|---|---|---|
| Feeds | collectors/feed_collector.py |
Unread RSS/Atom entries from Miniflux (self-hosted). Fetches via REST API, no MCP needed. |
| Health | collectors/health_collector.py |
Per-project status from existing monitoring scripts: up/down, error count, last deploy. |
| Cloudflare | collectors/health_scripts/cloudflare_health.py |
Workers error rate and request count (GraphQL), Pages deployment status (REST API). |
| Coolify | collectors/health_scripts/coolify_health.py |
Application, service, and database status via Coolify API (through Cloudflare Tunnel). |
| Tasks | collectors/task_collector.py |
Open tasks from Obsidian vault (Dataview checkbox format), synced to SQLite. |
All calendars accessible through a single MCP connection via calendar sharing:
| Calendar | Access | Purpose |
|---|---|---|
user@example.com |
owner (primary) | Personal |
shared-reader@example.com |
reader | Project A |
other-reader@example.com |
reader | Project B |
freebusy-reader@example.com |
freeBusyReader | Project C |
| Luma | reader | Events |
Runs at 06:00 via launchd. A single claude -p session with MCP access collects Gmail and Calendar data, writes to cos.db. Then Python scripts collect Health and Task data.
# Overnight pipeline (or manual: ./run.sh)
claude -p prompts/collect.md --max-budget-usd 2.00 # Gmail + Calendar via MCP → cos.db
python collectors/feed_collector.py # Miniflux feeds → cos.db
python collectors/health_collector.py # Health scripts → cos.db
python collectors/task_collector.py # Obsidian tasks → cos.db
python renderer.py # cos.db → Obsidian Daily NoteFailure handling: If a source fails, others continue. The Daily Note shows a warning for the failed source.
Runs after collection. Uses Claude Sonnet (claude -p, non-interactive) with a $1.50 budget cap.
Reads pending items from cos.db and classifies each:
| Class | Meaning | Example |
|---|---|---|
| DISPATCH | AI handles fully | Meeting confirmation reply, research task, note update |
| PREP | AI does 80%, you finish | Complex email draft, error investigation summary + fix direction |
| YOURS | Needs your brain | Strategy decisions, pricing, live meetings |
| SKIP | Not today | Low priority, blocked, deadline far away |
Classification is written back to cos.db and rendered into the Daily Note. When you wake up, the sorted plan is already waiting.
Runs inside run_collect after feeds land in cos.db. Learns what you actually care about from your own labels and implicit signals, scores every feed item, and tiers them for the brief.
Pipeline per item:
- Language detected at ingestion (
langdetect, tr/en kept, others parked). - Embedded via Ollama
multilingual-e5-largeover Tailscale (Mac mini inference, zero API cost). - Scored against two centroids:
cos(item, relevant) - cos(item, not_relevant). - Placed in one of four buckets:
| Bucket | Threshold | Where it goes |
|---|---|---|
high_keep |
score >= +0.010 | Daily brief, top-of-fold |
auto_keep |
score >= +0.002 | Weekly digest |
borderline |
in between | Labeling queue (active learning) |
auto_drop |
score <= -0.001 | Silently dropped |
Label sources, priority order:
- Explicit labels via
cos taste labelorcos taste weekly(interactive terminal) - Miniflux starred entries (nightly auto-sync)
- Vault URL cross-reference (periodic)
Explicit not_relevant always beats any implicit positive signal.
At 10.8k feed items and ~160 relevant / 85 not_relevant labels, the filter achieves 95% precision on auto_keep with 79% recall, auto-deciding ~90% of incoming volume. See ADR-001 for details and alternatives considered.
On-demand. You trigger it when ready. collectors/orchestrator.py dispatches domain agents in parallel using asyncio, with semaphore-based concurrency control (max_workers in config). Each agent has its own budget cap. Use --sequential flag for debugging.
- Reads the classified Daily Note
- Shows the plan, you approve or adjust
- Fires subagents in parallel for approved DISPATCH + PREP tasks
| Agent | Scope | Model | Budget | Safety |
|---|---|---|---|---|
| Calendar Agent | Meeting prep notes | Sonnet | $0.50 | Read-only |
| Health Agent | Error analysis + fix direction | Sonnet | $0.50 | No code patches |
| Task Agent | Task completion notes, research | Sonnet | $0.50 | Scoped vault folders |
| Feed Agent | Actionable feed summaries | Sonnet | $0.50 | Scoped vault folders |
Completion report appends to the Daily Note. Task statuses update in cos.db.
On-demand. Triggered after the Morning Sweep. Uses Claude Sonnet.
Takes remaining YOURS + PREP tasks and fits them into calendar free blocks:
- Calendly slots are untouchable
- Dev work in the morning (deep work)
- Content in the afternoon
- Email/admin in gaps and end of day
- P1 tasks always first
Writes [TB] prefixed events to a dedicated "AI Plan" Google Calendar via MCP. Supports --dry-run for preview before writing.
The overnight process produces a ready-to-review briefing:
# 2026-03-07
## Calendar
- 10:00-11:00 Client X meeting (Calendly) - prep needed
- 14:00-14:30 Deploy review
- Free: 07:00-10:00, 11:00-14:00, 14:30-18:00
## Project Status
- OK: cf:worker-1, cf:site-1, coolify:app-1, coolify:db-1
- coolify:old-service: error (exited:unhealthy)
## Classified Tasks
### DISPATCH (AI handles)
- [ ] Client A email reply - meeting confirmation (#email)
- [ ] Blog post research - framework migration (#content)
### PREP (80% ready, you finish)
- [ ] Client Y hosting migration reply - draft ready (#email)
- [ ] project-d timeout - summary + fix direction (#dev)
### YOURS (your brain needed)
- [ ] Client X meeting prep
- [ ] project-e checkout flow fix (#dev)
### SKIP (not today)
- [ ] project-d onboarding wizard - P3, deadline far
## Carried Over
- [ ] [P2] Blog post publish - pending 2 dayschief-of-staff/
├── cos/ # Core library
│ ├── config.py # TOML config loader
│ ├── db.py # SQLite access layer (inserts, queries, cleanup)
│ ├── log.py # Structured JSON logging
│ ├── language.py # TR/EN detection for feed items
│ └── taste.py # Taste filter: embed, centroid, score
├── collectors/ # Data collection + pipeline stages
│ ├── calendar_collector.py # Google Calendar MCP response parser
│ ├── gmail_collector.py # Gmail MCP response parser
│ ├── feed_collector.py # Miniflux REST API collector
│ ├── feed_backfill.py # One-shot paginated Miniflux backfill
│ ├── task_collector.py # Obsidian vault task scanner
│ ├── health_collector.py # Project health script runner
│ └── health_scripts/
│ ├── cloudflare_health.py # Workers analytics + Pages deployments
│ └── coolify_health.py # Apps, services, databases via Coolify API
│ ├── radar_collector.py # Opportunity Radar signal importer
│ ├── classifier.py # Classification export/import CLI
│ ├── sweep.py # Sweep export/record/complete CLI
│ ├── orchestrator.py # Parallel sweep orchestrator (asyncio)
│ ├── taste_label.py # Interactive labeling TUI (active learning)
│ ├── taste_weekly.py # Weekly check-in (uncertainty + sanity + rescue)
│ ├── taste_starred_sync.py # Miniflux starred -> relevant auto-label
│ ├── vault_label.py # Vault URL scan -> relevant auto-label
│ ├── taste_feed_audit.py # Per-feed noise/signal audit
│ └── taste_report.py # Full-corpus metrics report
├── prompts/ # Claude system prompts
│ ├── collect.md # MCP collection instructions
│ ├── classifier.md # Classification rules + decision framework
│ ├── sweep.md # Monolithic sweep prompt (fallback)
│ ├── brief.md # Turkish daily brief template
│ └── agents/ # Domain-specific subagent prompts
│ ├── email-agent.md # Email draft creation
│ ├── calendar-agent.md # Meeting prep notes
│ ├── health-agent.md # Error analysis + fix direction
│ ├── task-agent.md # Task completion notes
│ └── feed-agent.md # Feed summary + action items
├── tests/ # pytest unit tests (7 modules)
├── schema.sql # SQLite schema
├── migrations/ # Schema migrations (taste filter, language, tiers)
├── renderer.py # SQLite -> Obsidian Daily Note
├── run.sh # Pipeline orchestrator (mutex, step routing)
├── cos-brief.sh # Unified CLI (brief, run, status, weekly, insights, cleanup, taste)
├── setup_wizard.py # Interactive setup (config + db + launchd)
├── config.toml # User config (gitignored)
└── config.example.toml # Config template
- macOS (launchd for scheduling)
- Python 3.11+
- Claude Code with Max/Pro subscription
- Claude Code MCP connectors authenticated:
claude.ai Gmail(connect via/mcpin Claude Code)claude.ai Google Calendar(connect via/mcpin Claude Code)
- Obsidian vault with Dataview plugin
- Existing project health monitoring scripts (optional, for Health Collector)
git clone https://github.com/ceaksan/chief-of-staff.git ~/.chief-of-staff
cd ~/.chief-of-staff
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txtThe interactive wizard walks you through configuration, initializes the database, and sets up scheduling:
python setup_wizard.pyIt reads config.example.toml as a template and generates config.toml with your values. Each step is skippable with sensible defaults. Optional integrations (Miniflux, Coolify, Cloudflare, healthchecks.io) are prompted separately.
To validate an existing config:
python setup_wizard.py --validatecp config.example.toml config.toml
# Edit config.toml with your paths and rules
sqlite3 cos.db < schema.sqlNo API keys or Google Cloud Console needed. Claude Code handles OAuth via its built-in MCP connectors.
# In Claude Code, authenticate both services:
/mcp
# Select "claude.ai Gmail" → Authenticate
# Select "claude.ai Google Calendar" → Authenticate
# Verify:
# Gmail: gmail_get_profile should return your email
# Calendar: gcal_list_calendars should show your calendarsFor multi-calendar access, share other Google account calendars to your primary account (Google Calendar > Settings > Share with specific people > your primary email).
See config.example.toml for all available settings. Key sections:
[paths]
obsidian_vault = "/path/to/your/vault"
daily_notes_dir = "Daily"
[calendars]
ids = ["primary", "shared@example.com"]
[claude]
collector_budget = 2.00 # per-run budget cap
classifier_budget = 1.50
sweep_budget = 3.00
[schedule]
collector_time = "06:00"
[Scan report · 2026-09-13
- ✓ Prohibited terms or links
- ✓ Repository eligibility
- ✓ slopscore.md paperwork
- ✓ Content policy
- ✓ Risk review
From the balcony · 0 of 1 clapped
Princess read it and passed. Their reasons are on the balcony, with every other verdict.
Critics are accounts on this site with no GitHub account behind them. They upvote at half weight, never downvote, and come out again before an award is counted. Who they are.
report this listing
— log in to report
0 comments
log in to comment.