sweetie 0.1.7
Autonomous AI coding agent that works off a task queue, switches between tasks when blocked, and delivers PRs
License: MIT
Requires Python: >=3.11
Keywords: ai, agent, coding, automation, notion, claude

# SWEetie

sweetie: Self-managing Worker for Engineering Excellence, Tasks, Implementation, and Execution. AKA your own personal AI SWE, AKA SWE-etie

Autonomous AI coding agent that pulls tasks from Notion, works on them via Claude Code, notifies you on Slack when blocked, switches to the next task, and delivers PRs when done.

## How it works

```
Poll Notion → Claim task → Create branch → Run Claude Code
                                                ↓
                                          Success? → Run tests → Create PR
                                          Blocked? → Notify Slack → Pick next task
```

Sweetie runs as a single-process daemon. It polls your Notion task board, picks the highest-priority queued task, creates a branch, and hands it to Claude Code. If Claude gets stuck, Sweetie messages you on Slack and moves on to the next task. When a task is done, it opens a PR for your review.

## Prerequisites

- Python 3.11+
- [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code) installed and authenticated

## Install

```bash
# From PyPI (recommended)
pip install sweetie

# Or with pipx for an isolated install
pipx install sweetie

# Or from source
git clone https://github.com/maryanngong/sweetie.git
cd sweetie
pip install -e .
```

## Quick start

1. Create a `sweetie.yaml` config file in your workspace. If you create it in the repo you want Sweetie to work on, make sure to add it to your .gitignore so sensitive credentials do not get pushed to git.

```bash
cd /path/to/my-repo

# If you installed from source, copy the example:
cp /path/to/sweetie/sweetie.example.yaml sweetie.yaml

# If you installed from PyPI, create one manually or download the example:
curl -O https://raw.githubusercontent.com/maryanngong/sweetie/main/sweetie.example.yaml
mv sweetie.example.yaml sweetie.yaml
```

Sweetie looks for `sweetie.yaml` in the current directory when you run `sweetie start`. You can also specify a path with `sweetie start --config /path/to/sweetie.yaml`.

2. Fill in your Notion, Slack, and Github tokens (see [Integration setup](#integration-setup) below).

3. Start sweetie from inside your repo:

```bash
cd /path/to/my-repo
sweetie start
```

## Integration setup

Sweetie connects to three services. Here's what you need for each.

### Notion

**What you need:** An API key and a database ID.

1. Go to [notion.so/my-integrations](https://www.notion.so/my-integrations) and create a new integration.
2. Grant it **Read content**, **Update content**, and **Insert content** capabilities.
3. Copy the **Internal Integration Secret** — this is your `api_key`.
4. Open your task database in Notion. Make sure it has all required columns.
4. To get your datasource ID, click the settings for that table view. In the menu, under `Data source settings`, select `Manage data sources`, find your data source, click  `...` menu, and `Copy data source ID`.


```yaml
notion:
  api_key: "ntn_..."        # or "${NOTION_API_KEY}" to use an env var
  database_id: "313b5941-b260-1234-aabb-000b06a6f452"
```

**Database columns:** Your Notion database needs at minimum these columns (names are configurable via `columns:`):

| Column | Type | Description |
|--------|------|-------------|
| Task | Title | Task name |
| Description | Rich Text | What to implement |
| Status | Status | Queued, In Progress, Blocked, In Review, Done, Failed |
| Priority | Select | P0, P1, P2 (or your own values) |

Sweetie will also read/write these optional columns:

| Column | Type | Used by |
|--------|------|---------|
| Agent Branch | Rich Text | Sweetie will write the git branch it created |
| Agent PR | URL | Sweetie will write the link to the PR |
| Agent Notes | Rich Text | Sweetie will write status updates and summaries |
| Blocked Reason | Rich Text | Sweetie will write the question it needs answered |
| Cost | Number | Sweetie writes API cost in USD |
| Slack Thread TS | Rich Text | Sweetie will write Slack thread timestamp for reply monitoring |
| Slack Channel | Rich Text | Sweetie will write Slack channel ID for reply monitoring |
| Repo | Rich Text | You can specify a path to the repo for this task |
| Test Command | Rich Text | You can override the test command per task |

If your column names or status values differ from the defaults, map them in config:

```yaml
notion:
  columns:
    title: "Name"           # default: "Task"
    status: "Status"
  status_values:
    queued: "Ready"          # default: "Queued"
    in_progress: "Working"   # default: "In Progress"
  priority_values: ["High", "Medium", "Low"]  # default: ["P0", "P1", "P2"]
```

### Slack

Sweetie uses Slack to notify you when tasks are started, completed, blocked, or failed.

**Basic setup (notifications only):** Create a [Slack incoming webhook](https://api.slack.com/messaging/webhooks) and add the URL to config:

```yaml
slack:
  webhook_url: "https://hooks.slack.com/services/..."
```

**Thread reply monitoring (recommended):** If you want to unblock tasks by replying directly in the Slack thread, you also need a Slack Bot Token:

1. Go to [api.slack.com/apps](https://api.slack.com/apps) and create a new app (or use the one from your webhook).
2. Under **OAuth & Permissions**, add these **Bot Token Scopes**:
   - `chat:write` — to post messages and get thread timestamps
   - `channels:history` — to read thread replies
3. Install (or reinstall) the app to your workspace and copy the **Bot User OAuth Token** (`xoxb-...`).
4. **Add the bot to your channel:** Go to the channel in Slack and type `/invite @YourAppName`, or click the channel name → **Integrations** → **Add apps**. The bot must be in the channel to post messages and read replies.
5. Get the channel ID: right-click the channel name → **View channel details** → copy the ID at the bottom (e.g. `C0AF0LZEEFF`).

> **Note:** If you add scopes later, you must **reinstall the app** to your workspace for the new scopes to take effect. Slack requires this after any scope change.

```yaml
slack:
  webhook_url: "https://hooks.slack.com/services/..."
  bot_token: "xoxb-..."     # or "${SLACK_BOT_TOKEN}"
  channel: "C0AF0LZEE64"
```

When a task is blocked, Sweetie will post a message with the question. Reply in the thread, and Sweetie will automatically pick up your answer and resume the task when it looks for its next task.

### GitHub

Sweetie needs a GitHub token to push branches and create PRs.

1. Go to [github.com/settings/tokens](https://github.com/settings/tokens) and create a **Fine-grained personal access token**.
2. Grant it access to the repos Sweetie will work on.
3. Under **Repository permissions**, enable:
   - **Contents** — Read and write (to push branches)
   - **Pull requests** — Read and write (to create PRs)

Provide the token in one of these ways (checked in order):

```yaml
# Option 1: In sweetie.yaml
github:
  token: "github_pat_..."   # or "${GITHUB_TOKEN}"
```

```bash
# Option 2: Environment variable
export GITHUB_TOKEN="github_pat_..."
# or
export GH_TOKEN="github_pat_..."
```

## Usage

```bash
# Start the daemon from your repo directory
cd /path/to/my-repo
sweetie start

# Start with a clean session (resets cost tracking, blocked tasks, etc.)
sweetie start --fresh

# Start with a custom config path
sweetie start --config ./my-config.yaml

# Start with debug logging
sweetie start --verbose

# Validate your config file
sweetie validate

# Check current agent status (blocked tasks, cost, etc.)
sweetie status

# Re-queue a blocked task from the CLI
sweetie unblock 303b5941    # prefix match on task ID

# Reset session state
sweetie reset
```

## File locations

| File | Location | Purpose |
|------|----------|---------|
| `sweetie.yaml` | Created by you - Current directory (or `--config` path) | Configuration — tokens, settings, repo config |
| `~/.sweetie/state.json` | Home directory | Written by Sweetie - Session state — active task, blocked tasks, cost tracking |

The config file lives with your repo so different projects can have different settings. The state file is global so `sweetie status` works from any directory.

**Important:** `sweetie.yaml` contains sensitive tokens. Add it to your `.gitignore`:

```bash
echo "sweetie.yaml" >> .gitignore
```

## How tasks flow

1. You add a task to Notion with status **Queued**
2. Sweetie picks it up, sets status to **In Progress**, creates a `sweetie-agent/<task-name>` branch
3. Claude Code implements the task
4. If blocked → status **Blocked**, Slack notification with the question, moves to next task
5. If done → runs tests (if configured), creates PR, status **In Review**
6. You review the PR and merge

### Unblocking a task

Three ways to unblock a blocked task:

- **Slack thread reply** (if `bot_token` + `channel` configured) — reply in the thread and Sweetie picks it up automatically
- **CLI** — run `sweetie unblock <task-id>`
- **Notion** — change the status back to Queued manually

### Re-queuing after PR review

If you review a PR and want changes:

1. Leave review comments on the PR (inline or general)
2. Optionally push your own commits to the branch
3. Set the task status back to **Queued** in Notion

Sweetie will pick it up, pull the latest branch, fetch all PR comments, and pass them to Claude as context.

## Model selection

Choose which Claude model Sweetie uses in your config:

```yaml
agent:
  model: "opus"   # Options: "sonnet", "opus", "haiku"
```

- **opus** — most capable, best for complex multi-file tasks, highest cost
- **sonnet** — good balance of capability and cost (default)
- **haiku** — fastest and cheapest, good for simple/straightforward tasks

## Budget limits

Sweetie tracks API costs and enforces two budget limits:

```yaml
agent:
  max_cost_per_task_usd: 5.00    # Max spend on a single task
  max_cost_per_session_usd: 50.00 # Max total spend before auto-shutdown
```

- **Per-task budget** — if a single task exceeds this, Sweetie stops working on it and marks it failed.
- **Per-session budget** — once the total spend across all tasks hits this limit, Sweetie sends a Slack notification and shuts down. Use `sweetie start --fresh` to reset the session cost counter.

You can check current spend at any time with `sweetie status`.

## Configuration reference

Full example in `sweetie.example.yaml`. Key sections:

```yaml
# Agent behavior
agent:
  model: "sonnet"              # Claude model (sonnet, opus, haiku)
  max_turns_per_task: 50       # Max Claude turns before stopping
  max_retries_on_test_fail: 2  # Retry count when tests fail
  max_cost_per_task_usd: 5.00  # Budget per task
  max_cost_per_session_usd: 50 # Total session budget
  poll_interval_seconds: 30    # How often to poll Notion for new tasks
  loop_delay_seconds: 5        # Pause between main loop iterations

# Per-repo settings (optional — defaults to current directory)
repos:
  "/path/to/repo":
    test_command: "npm test"     # Run after task completion
    lint_command: "npm run lint" # Optional lint step
    base_branch: "main"         # Branch to base work off of
    allowed_tools:               # Extra Bash commands Claude can use
      - "npm install"

# Safety guardrails
safety:
  blocked_bash_patterns:         # Commands Claude is never allowed to run
    - "rm -rf"
    - "git push --force"
    - "DROP TABLE"
  require_tests_pass: true       # Block PR if tests fail
  require_lint_pass: false       # Block PR if lint fails
```

### Environment variable interpolation

Any string value in `sweetie.yaml` can reference environment variables with `${VAR_NAME}`:

```yaml
notion:
  api_key: "${NOTION_API_KEY}"
slack:
  webhook_url: "${SLACK_WEBHOOK_URL}"
github:
  token: "${GITHUB_TOKEN}"
```

## Troubleshooting

### Slack thread replies aren't picked up

Sweetie needs `bot_token` and `channel` in your Slack config to monitor thread replies. Without them, blocked notifications go via webhook (which is send-only) and Sweetie can't read replies.

```yaml
slack:
  webhook_url: "https://hooks.slack.com/services/..."
  bot_token: "xoxb-..."       # Required for thread monitoring
  channel: "C0AF0LZEE64"      # Channel ID, not name
```

### Slack API error: "missing_scope"

Your Slack bot token is missing required scopes. Go to [api.slack.com/apps](https://api.slack.com/apps) → your app → **OAuth & Permissions** → add `chat:write` and `channels:history` under **Bot Token Scopes**. After adding scopes, you must **reinstall the app** to your workspace for them to take effect.

### Slack bot not posting messages

The bot must be added to the channel. In Slack, go to the channel and type `/invite @YourAppName`, or click the channel name → **Integrations** → **Add apps**.

### PR creation fails with 404 for outside collaborators

GitHub fine-grained PATs can only access repos owned by the token creator or their organizations. If the collaborator is an outside contributor (not an org member), the repo won't appear when creating a fine-grained token. Two fixes:

- **Invite them as an org member** (not just repo collaborator) so the repo appears in their fine-grained PAT settings
- **Use a classic PAT** with the `repo` scope instead — classic tokens work on any repo the user has access to

### PR creation fails with "not all refs are readable"

This happens when your git token does not have access to the repo or if GitHub hasn't finished processing a push. Sweetie retries automatically (3 attempts with backoff), but if it still fails, try running `sweetie start` again — the task will be re-attempted.

### Claude can't write files or use tools

Claude Code in pipe mode (`-p`) requires tools to be explicitly allowed. Sweetie passes a default set of allowed tools (Read, Write, Edit, Bash, Glob, Grep, WebSearch, WebFetch, NotebookEdit). If you've set `allowed_tools` in your repo config, make sure it includes everything your Claude setup needs.

### Notion Cost column errors

The Cost column in your Notion database must be of type **Number**. If it's set to Rich Text or another type, Sweetie will fail when writing cost data.

## License

MIT License