Metadata-Version: 2.4
Name: agenticore
Version: 0.7.0
Summary: Claude Code runner and orchestrator — thin job lifecycle, repo management, and OTEL pipeline
Author: The Cloud Clock Work
License: MIT
Project-URL: Homepage, https://github.com/The-Cloud-Clock-Work/agenticore
Project-URL: Repository, https://github.com/The-Cloud-Clock-Work/agenticore
Project-URL: Issues, https://github.com/The-Cloud-Clock-Work/agenticore/issues
Project-URL: Documentation, https://github.com/The-Cloud-Clock-Work/agenticore/tree/main/docs
Project-URL: Changelog, https://github.com/The-Cloud-Clock-Work/agenticore/blob/main/CHANGELOG.md
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Operating System :: OS Independent
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
Classifier: Topic :: Software Development :: Build Tools
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: fastmcp>=2.0
Requires-Dist: redis>=7.0
Requires-Dist: uvicorn[standard]>=0.30
Requires-Dist: httpx>=0.25
Requires-Dist: pyyaml>=6.0
Requires-Dist: langfuse<3,>=2.0
Requires-Dist: PyJWT[crypto]>=2.8
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: pytest-cov>=4.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
Requires-Dist: fakeredis>=2.21; extra == "dev"
Requires-Dist: ruff>=0.4; extra == "dev"
Dynamic: license-file

# Agenticore

Production-grade Claude Code runner and orchestrator. Submit a task, get a PR.

[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/The-Cloud-Clock-Work/agenticore/blob/main/LICENSE)
[![Tests](https://github.com/The-Cloud-Clock-Work/agenticore/actions/workflows/test.yml/badge.svg)](https://github.com/The-Cloud-Clock-Work/agenticore/actions/workflows/test.yml)
[![Docker](https://img.shields.io/docker/v/tccw/agenticore?label=Docker%20Hub)](https://hub.docker.com/r/tccw/agenticore)
[![Helm](https://img.shields.io/badge/Helm-GHCR-blue)](https://ghcr.io/the-cloud-clock-work/charts/agenticore)
[![PyPI](https://img.shields.io/pypi/v/agenticore)](https://pypi.org/project/agenticore/)
[![Python 3.12+](https://img.shields.io/badge/python-3.12%2B-blue.svg)](https://python.org)

```
MCP Client / REST Client / CLI
            │
            ▼
    ┌── Agenticore ──────────────────────────────────────────────┐
    │   Auth · Router · Job Queue                                │
    │                                                            │
    │   Clone repo ──► Materialize profile ──► claude --worktree │
    │   (cached, distributed lock)   (.claude/ + .mcp.json)     │
    │                                         │                  │
    │                                         ▼                  │
    │                                   Auto-PR (gh)             │
    │                                   Job result → Redis       │
    └──────────────────────┬─────────────────────────────────────┘
                           │
                    OTEL Collector
                    → Langfuse / PostgreSQL
```

---

Agenticore is a **thin orchestration layer** on top of Claude Code:

- Accepts tasks from **MCP clients, REST, or CLI** — same API surface, one port
- **Clones and caches repos**, serializes concurrent access with distributed locks
- **Applies execution profiles** — `.claude/` config, `.mcp.json`, hooks, skills
- Spawns `claude --worktree -p "<task>"` and **opens a PR when it succeeds**
- Ships **full OTEL traces** (prompts, tool calls, token counts) to Langfuse / PostgreSQL
- Runs standalone, in Docker, or on **Kubernetes** (Helm chart, KEDA autoscaling, graceful drain)

---

## Install

```bash
pip install agenticore
```

Or from source:

```bash
git clone https://github.com/The-Cloud-Clock-Work/agenticore.git
cd agenticore
pip install -e .
```

---

## Quickstart

```bash
# 1. Set your credentials
export ANTHROPIC_API_KEY=sk-ant-...
export GITHUB_TOKEN=ghp_...

# 2. Start the server
agenticore serve

# 3. Submit a task
agenticore run "fix the null pointer in auth.py" \
  --repo https://github.com/org/repo \
  --wait
```

---

## CLI Commands

| Command | Description |
|---------|-------------|
| `agenticore run "<task>" --repo <url>` | Submit a task (returns job ID immediately) |
| `agenticore run "<task>" --repo <url> --wait` | Submit and wait for completion |
| `agenticore jobs` | List recent jobs |
| `agenticore job <id>` | Get job details, output, and PR URL |
| `agenticore cancel <id>` | Cancel a running job |
| `agenticore profiles` | List available execution profiles |
| `agenticore serve` | Start the server |
| `agenticore status` | Check server health |
| `agenticore version` | Show version |
| `agenticore update` | Update to latest version |
| `agenticore init-shared-fs` | Initialise shared filesystem (Kubernetes) |
| `agenticore drain` | Drain pod before shutdown (Kubernetes) |
| `agenticore hooks sync [--url URL]` | Clone/fetch agentihooks and rebuild profiles |

```bash
# Submit a task
agenticore run "fix the null pointer in auth.py" \
  --repo https://github.com/org/repo \
  --profile code

# Wait for result and see PR URL
agenticore run "add unit tests for the parser" \
  --repo https://github.com/org/repo \
  --wait

# Check a specific job
agenticore job a1b2c3d4-e5f6-7890-abcd-ef1234567890
```

---

## MCP Tools

Connect any MCP-compatible client and use these 5 tools:

| Tool | Parameters | Description |
|------|-----------|-------------|
| `run_task` | `task`, `repo_url`, `profile`, `base_ref`, `wait`, `session_id` | Submit a task for Claude Code execution |
| `get_job` | `job_id` | Get status, output, and PR URL for a job |
| `list_jobs` | `limit`, `status` | List recent jobs |
| `cancel_job` | `job_id` | Cancel a running or queued job |
| `list_profiles` | — | List available execution profiles |

All tools return the same JSON structure as the REST endpoints.

---

## REST API

```bash
# Submit a job (async — returns immediately with job ID)
curl -X POST http://localhost:8200/jobs \
  -H "Content-Type: application/json" \
  -d '{"task": "fix the auth bug", "repo_url": "https://github.com/org/repo"}'

# Submit and wait for completion
curl -X POST http://localhost:8200/jobs \
  -H "Content-Type: application/json" \
  -d '{"task": "fix the auth bug", "repo_url": "https://github.com/org/repo", "wait": true}'

# Get job status, output, and PR URL
curl http://localhost:8200/jobs/{job_id}

# List jobs (with optional filters)
curl "http://localhost:8200/jobs?limit=10&status=running"

# Cancel a job
curl -X DELETE http://localhost:8200/jobs/{job_id}

# List profiles
curl http://localhost:8200/profiles

# Health check (no auth required)
curl http://localhost:8200/health
```

---

## Connecting MCP Clients

Agenticore exposes two MCP transports on the same port:

| Transport | Endpoint | Use Case |
|-----------|----------|----------|
| Streamable HTTP | `/mcp` | `type: "http"` — Claude Code, Claude Desktop, most clients |
| SSE | `/sse` | Legacy SSE clients |
| stdio | stdin/stdout | Direct Claude Code subprocess integration |

### Claude Code CLI / Claude Desktop

Add to your project's `.mcp.json` or `~/.mcp.json`:

```json
{
  "mcpServers": {
    "agenticore": {
      "type": "http",
      "url": "http://localhost:8200/mcp"
    }
  }
}
```

### With API Key Authentication

```json
{
  "mcpServers": {
    "agenticore": {
      "type": "http",
      "url": "http://your-server:8200/mcp",
      "headers": {
        "X-API-Key": "your-secret-key"
      }
    }
  }
}
```

### stdio (Claude Code subprocess)

```json
{
  "mcpServers": {
    "agenticore": {
      "command": "python",
      "args": ["-m", "agenticore"]
    }
  }
}
```

---

## Authentication

Authentication is **optional**. When disabled, all endpoints are public.

### API Keys

```bash
# Via environment variable (comma-separated for multiple keys)
AGENTICORE_API_KEYS="key-1,key-2" agenticore serve
```

Pass the key in any of these ways:

| Method | Example |
|--------|---------|
| Header | `curl -H "X-Api-Key: key-1" http://localhost:8200/jobs` |
| Query param | `curl "http://localhost:8200/jobs?api_key=key-1"` |
| Bearer token | `curl -H "Authorization: Bearer key-1" http://localhost:8200/jobs` |

The `/health` endpoint is always public regardless of auth settings.

---

## Profiles

Profiles are **directory packages** that configure how Claude Code runs. Each profile
is a self-contained `.claude/` tree that Agenticore copies into the job's working
directory before spawning Claude.

```
<profiles-dir>/{name}/
├── profile.yml          ← Agenticore metadata (model, turns, auto_pr, timeout…)
├── .claude/
│   ├── settings.json    ← Hooks, tool permissions, env vars
│   ├── CLAUDE.md        ← System instructions for Claude
│   ├── agents/          ← Custom subagents
│   └── skills/          ← Custom slash-command skills
└── .mcp.json            ← MCP server config merged into the job
```

### Profile discovery

Profiles are **not bundled with Agenticore**. They live in two places:

| Source | Path | Set via |
|--------|------|---------|
| agentihooks integration | `{AGENTICORE_AGENTIHOOKS_PATH}/profiles/` | `AGENTICORE_AGENTIHOOKS_PATH` env var |
| User profiles | `~/.agenticore/profiles/` | Always checked |

Later sources override earlier ones when names collide.

### Profile inheritance

```yaml
# ~/.agenticore/profiles/code-strict/profile.yml
name: code-strict
extends: code          # inherits all settings from 'code'

claude:
  max_turns: 20
  effort: high
```

Child values override parent defaults. `.claude/` files are layered (child overlays
parent) during materialization.

### What Agenticore does with a profile at job start

1. Resolves the `extends` chain
2. Copies `.claude/` into the working directory (or `/shared/jobs/{id}/` in Kubernetes)
3. Merges `.mcp.json` with any existing `.mcp.json` in the repo
4. Translates `profile.yml` `claude:` fields into CLI flags:
   `--worktree --model sonnet --max-turns 80 --permission-mode bypassPermissions …`

Full profile reference: [Profile System docs](docs/architecture/profile-system.md)

---

## Helm (Kubernetes)

Agenticore ships a production-ready Helm chart published to GHCR. The chart
deploys a **StatefulSet** with a **shared RWX PVC** (NFS / EFS / Azure Files / Ceph)
so all pods share the same repo cache and job state, with **KEDA autoscaling**
based on Redis queue depth and **graceful drain** on pod shutdown.

```
Internet ──► LoadBalancer :8200
                    │
     ┌──────────────▼──────────────────────────┐
     │  Agenticore StatefulSet (0..N pods)      │
     │  Work-stealing from Redis queue          │
     └──────────┬──────────────────────────────┘
                │                │
         ┌──────▼───────┐  ┌─────▼───────────┐
         │  Redis        │  │  Shared RWX PVC  │
         │  jobs · locks │  │  /shared/        │
         │  KEDA queue   │  │  ├─ repos/       │
         └───────────────┘  │  ├─ jobs/        │
                            │  └─ job-state/   │
         KEDA ScaledObject  └─────────────────┘
         watches Redis queue
```

### Install

```bash
# 1. Create the Kubernetes Secret (once per cluster)
kubectl create secret generic agenticore-secrets \
  --from-literal=redis-url="redis://:password@redis:6379" \
  --from-literal=redis-address="redis:6379" \
  --from-literal=anthropic-api-key="sk-ant-..." \
  --from-literal=github-token="ghp_..."

# 2. Install the chart
helm install agenticore \
  oci://ghcr.io/the-cloud-clock-work/charts/agenticore \
  --set storage.className=your-rwx-storage-class
```

### Key values

| Value | Default | Description |
|-------|---------|-------------|
| `storage.className` | `nfs-client` | RWX storage class (required) |
| `storage.size` | `100Gi` | PVC size |
| `replicas` | `2` | Static replica count (ignored when KEDA enabled) |
| `image.tag` | `latest` | Container image tag |
| `config.defaultProfile` | `code` | Default execution profile |
| `config.maxParallelJobs` | `3` | Max Claude subprocesses per pod |
| `keda.enabled` | `false` | Enable KEDA autoscaling |
| `keda.minReplicas` | `1` | KEDA min replicas |
| `keda.maxReplicas` | `10` | KEDA max replicas |
| `ingress.enabled` | `false` | Enable Ingress resource |
| `ingress.host` | `agenticore.example.com` | Ingress hostname |

### Autoscaling with KEDA

```bash
helm upgrade agenticore \
  oci://ghcr.io/the-cloud-clock-work/charts/agenticore \
  --set keda.enabled=true \
  --set keda.redisAddress=redis:6379 \
  --set keda.maxReplicas=20
```

A `ScaledObject` watches the Redis job queue and adds one pod per 5 pending jobs.

### Graceful drain

StatefulSet pods run `agenticore drain --timeout 270` as a PreStop hook. Drain:
1. Marks the pod as draining in Redis (new jobs route elsewhere)
2. Waits for all in-progress jobs to finish
3. Exits cleanly — Kubernetes then sends SIGTERM

Full Kubernetes guide: [Kubernetes Deployment](docs/deployment/kubernetes.md)

---

## Docker

```bash
# Local dev — full stack (Agenticore + Redis + PostgreSQL + OTEL Collector)
cp .env.example .env
docker compose up --build -d

# Production — Agenticore only (point at your managed services)
docker run -d \
  -p 8200:8200 \
  -e AGENTICORE_TRANSPORT=sse \
  -e AGENTICORE_HOST=0.0.0.0 \
  -e ANTHROPIC_API_KEY=sk-ant-... \
  -e REDIS_URL=redis://your-redis:6379/0 \
  -e GITHUB_TOKEN=ghp_... \
  tccw/agenticore
```

---

## Key Environment Variables

| Variable | Default | Description |
|----------|---------|-------------|
| `AGENTICORE_TRANSPORT` | `stdio` | `sse` for HTTP server, `stdio` for MCP pipe |
| `AGENTICORE_HOST` | `127.0.0.1` | Bind address |
| `AGENTICORE_PORT` | `8200` | Server port |
| `AGENTICORE_API_KEYS` | _(empty)_ | Comma-separated API keys (optional) |
| `ANTHROPIC_API_KEY` | _(empty)_ | Anthropic API key passed to Claude |
| `REDIS_URL` | _(empty)_ | Redis URL — omit for file-based fallback |
| `GITHUB_TOKEN` | _(empty)_ | GitHub token for auto-PR |
| `AGENTICORE_DEFAULT_PROFILE` | `code` | Profile when none specified |
| `AGENTICORE_CLAUDE_TIMEOUT` | `3600` | Max job runtime in seconds |
| `AGENTICORE_AGENTIHOOKS_PATH` | _(empty)_ | Explicit path to agentihooks repo (skips cloning) |
| `AGENTICORE_AGENTIHOOKS_URL` | _(empty)_ | Git URL to clone agentihooks from (supports `GITHUB_TOKEN`) |
| `AGENTICORE_AGENTIHOOKS_SYNC_INTERVAL` | `300` | Hot-reload interval in seconds (`0` disables) |
| `AGENTICORE_SHARED_FS_ROOT` | _(empty)_ | Shared FS root (Kubernetes mode) |

Full reference: [Configuration docs](docs/reference/configuration.md)

---

## Auth Broker

For environments where Claude Code jobs need **short-lived, human-authorized
tokens** (rather than long-lived static keys), Agenticore integrates with
**Auth Broker** — a companion service that handles OAuth flows on behalf of
running jobs.

Agenticore resolves Claude credentials in order:

1. **Auth Broker** (`AUTH_BROKER_URL`) — returns a Claude Max subscription token; `ANTHROPIC_BASE_URL` is cleared so Claude Code hits Anthropic directly (not the LiteLLM proxy).
2. **Static env** — `ANTHROPIC_API_KEY` + `ANTHROPIC_BASE_URL` as configured (typically pointing at the LiteLLM proxy).

When `AUTH_BROKER_URL` is set, the runner fetches credentials at job start
instead of reading them from environment variables. A pod requests a token,
Auth Broker notifies an operator, the operator approves via OAuth, and the
token lands in the job environment — no secret rotation required.

Auth Broker ships as a separate Docker image: `tccw/auth-broker`.

---

## OTEL Observability

Every job produces a Langfuse trace with spans for each Claude turn, including
prompts, tool calls, and token counts.

```bash
# Enable in .env
LANGFUSE_PUBLIC_KEY=pk-lf-...
LANGFUSE_SECRET_KEY=sk-lf-...
LANGFUSE_HOST=https://cloud.langfuse.com

AGENTICORE_OTEL_ENABLED=true
OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317
```

The bundled `docker-compose.yml` includes an OTEL Collector pre-wired to push
traces to both Langfuse (SDK) and PostgreSQL (raw spans).

Full setup: [OTEL Pipeline docs](docs/deployment/otel-pipeline.md)

---

## Documentation

- [Quickstart](docs/getting-started/quickstart.md)
- [Connecting Clients](docs/getting-started/connecting-clients.md)
- [Profile System](docs/architecture/profile-system.md)
- [Architecture Internals](docs/architecture/internals.md)
- [Job Execution](docs/architecture/job-execution.md)
- [Kubernetes Deployment](docs/deployment/kubernetes.md)
- [Docker Compose](docs/deployment/docker-compose.md)
- [OTEL Pipeline](docs/deployment/otel-pipeline.md)
- [CLI Reference](docs/reference/cli-commands.md)
- [API Reference](docs/reference/api-reference.md)
- [Configuration](docs/reference/configuration.md)

---

## Development

```bash
pip install -e ".[dev]"

# Tests
pytest tests/unit -v -m unit --cov=agenticore

# Lint
ruff check agenticore/ tests/
ruff format --check agenticore/ tests/
```
