Metadata-Version: 2.4
Name: sweat-agent
Version: 0.1.0
Summary: Digital twin agent — picks Asana tasks and implements them via Claude Code
Requires-Python: >=3.11
Description-Content-Type: text/markdown
Requires-Dist: claude-agent-sdk>=0.0.14
Requires-Dist: asana>=5.0.0
Requires-Dist: PyGithub>=2.1.1
Requires-Dist: GitPython>=3.1.41
Requires-Dist: python-dotenv>=1.0.0
Requires-Dist: opentelemetry-api>=1.20.0
Requires-Dist: opentelemetry-sdk>=1.20.0
Requires-Dist: opentelemetry-exporter-otlp-proto-grpc>=1.20.0

# sweat — Software Engineer August Tollerup

**sweat** is an autonomous software engineering agent. It runs on a schedule, picks unassigned tasks from Asana, implements them using Claude Code SDK, and opens GitHub pull requests — all without human intervention. A second agent reviews PRs, and a third audits code quality.

---

## How It Works

```
cli.py start
 ├── ImplementerAgent (x N replicas, every hour)
 │    ├── 1. Fetch unassigned tasks from Asana
 │    ├── 2. Filter by work type, domain, estimated time
 │    ├── 3. Ask Claude to pick the most feasible task
 │    ├── 4. Assign task + post proposal comment in Asana
 │    ├── 5. Clone target GitHub repo to a temp dir
 │    ├── 6. Run Claude Code SDK (headless) to implement
 │    ├── 7. Commit + push branch, open GitHub PR
 │    └── 8. Post PR link back to Asana, clean up
 │
 ├── ReviewerAgent (every 60s)
 │    ├── 1. Poll open PRs on configured repos
 │    ├── 2. Skip self-authored and already-reviewed PRs
 │    └── 3. Run Claude Code SDK to review, post via GitHub API
 │
 └── CodeReviewerAgent (daily)
      ├── 1. Scan repo against quality standards doc
      ├── 2. Identify findings, deduplicate against existing tasks
      └── 3. Create Asana tasks for new findings
```

---

## Project Structure

```
sweat/
├── cli.py                   # Entrypoint — runs all agent loops concurrently
├── config.py                # Env vars, agent definitions, project mappings
├── agents/
│   ├── base.py              # BaseAgent ABC with run_once()
│   ├── implementer.py       # Picks tasks, writes code, opens PRs
│   ├── reviewer.py          # Reviews open PRs
│   ├── code_reviewer.py     # Audits repo quality, creates tasks
│   └── registry.py          # Agent type registry
├── clients/
│   ├── asana.py             # Asana API wrapper
│   └── github.py            # GitHub API wrapper (PyGithub + GitPython)
├── agent.py                 # Claude Code SDK wrapper
├── task_selector.py         # Claude picks the most feasible task
├── task_filter.py           # Rule-based task filtering and sorting
├── task_claims.py           # In-memory lock to prevent duplicate claims
├── audit.py                 # Structured JSON audit log
├── telemetry.py             # OpenTelemetry traces + metrics (OTLP export)
├── prompts/
│   ├── task_prompt.py       # Implementation prompt builder
│   ├── review_prompt.py     # PR review prompt builder
│   └── code_review_prompt.py # Code quality audit prompt builder
├── knowledge/               # Persistent agent knowledge base
├── tests/                   # 96 unit tests (all external calls mocked)
├── Dockerfile               # Standalone container image
├── docker-compose.yml       # Single-service compose for deployment
└── pyproject.toml           # uv project config and dependencies
```

---

## Quick Start

### Prerequisites

- Python 3.11+
- [uv](https://docs.astral.sh/uv/) package manager
- [Claude Code](https://docs.anthropic.com/en/docs/claude-code) installed and authenticated
- An Asana personal access token
- A GitHub personal access token (or GitHub App, see below)

### Install

```bash
git clone <repo-url> && cd sweat
uv sync
```

### Configure

Copy `.env.example` to `.env` and fill in your credentials:

```bash
cp .env.example .env
```

```
ASANA_TOKEN=your_asana_personal_access_token
GITHUB_TOKEN=your_github_personal_access_token
ASANA_ASSIGNEE_GID=your_asana_user_gid
ANTHROPIC_API_KEY=your_anthropic_api_key
```

Then edit `config.py` to map your Asana projects to GitHub repos:

```python
AGENTS = [
    {
        "id": "impl-myproject",
        "type": "implementer",
        "interval": 3600,
        "asana_assignee_gid": os.environ["ASANA_ASSIGNEE_GID"],
        "projects": [
            {
                "asana_project_id": "1234567890",
                "github_repo": "org/repo",
                "branch_prefix": "agent/",
            }
        ],
    },
    {
        "id": "reviewer-myproject",
        "type": "reviewer",
        "interval": 60,
        "projects": [
            {
                "github_repo": "org/repo",
                "branch_prefix": "agent/",
            }
        ],
    },
]
```

### Run

```bash
# Start all agents (implementer + reviewer + code reviewer loops)
uv run python cli.py start

# Run reviewer agents once
uv run python cli.py review

# Run code review agents once
uv run python cli.py code-review

# View recent audit log
uv run python cli.py log --last 20

# Dry run (no side effects)
uv run python main.py --dry-run
```

Ctrl-C or `SIGTERM` shuts down cleanly.

---

## Docker

sweat ships as a standalone Docker image. It does **not** bundle any observability infrastructure — bring your own Grafana Cloud, Datadog, or self-hosted OTel collector.

```bash
# Build
docker compose build

# Run (credentials in .env)
docker compose up -d
```

The compose file mounts a persistent volume for the audit log at `/app/data/audit.jsonl`.

### Telemetry

sweat exports OpenTelemetry traces and metrics via OTLP gRPC. To enable, set `OTEL_EXPORTER_OTLP_ENDPOINT` in your `.env`:

```
OTEL_EXPORTER_OTLP_ENDPOINT=https://otlp.your-provider.com:4317
```

If the variable is unset, telemetry is silently disabled — no errors, no overhead.

**Exported metrics:**

| Metric | Type | Description |
|---|---|---|
| `sweat.agent.runs` | Counter | Total `run_once()` cycles |
| `sweat.agent.errors` | Counter | Failed `run_once()` cycles |
| `sweat.agent.run_duration_s` | Histogram | Duration per cycle |
| `sweat.tasks.implemented` | Counter | Tasks successfully implemented |
| `sweat.prs.opened` | Counter | PRs opened |
| `sweat.prs.reviewed` | Counter | PRs reviewed |
| `sweat.claude.calls` | Counter | Claude SDK invocations |
| `sweat.claude.call_duration_s` | Histogram | Claude SDK call duration |

---

## Testing

```bash
uv run pytest -v
```

96 tests, all run in under a second. No real API calls — Asana, GitHub, and Claude SDK are mocked at the interface boundary.

---

## Dependencies

| Package | Purpose |
|---|---|
| `claude-agent-sdk` | Task selection + headless Claude Code execution |
| `asana` | Asana REST API client |
| `PyGithub` | GitHub API (PR creation, reviews) |
| `GitPython` | Git operations (clone, branch, push) |
| `python-dotenv` | `.env` file loading |
| `opentelemetry-api` | Telemetry API |
| `opentelemetry-sdk` | Telemetry SDK |
| `opentelemetry-exporter-otlp-proto-grpc` | OTLP gRPC exporter |
