Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .claude/skills/conductor/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ conductor validate workflow.yaml # Validate only
conductor init my-workflow --template simple # Create from template
conductor templates # List templates
conductor stop # Stop background workflow
conductor update # Check for and install latest version
```

Progress output is shown by default. Use `-V` (verbose) for full prompts and detailed tool call info.
Expand Down
23 changes: 23 additions & 0 deletions .claude/skills/conductor/references/execution.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,29 @@ conductor stop --port 8080
conductor stop --all
```

### conductor update

Check for and install the latest version of Conductor:

```bash
conductor update
```

The command:
1. Fetches the latest release from the GitHub Releases API
2. Compares the remote version with the locally installed version
3. If a newer version is available, runs `uv tool install --force git+https://github.com/microsoft/conductor.git@v{version}` to upgrade
4. Clears the update-check cache on success so the next invocation re-checks cleanly

If already up to date, prints a confirmation message and exits.

**Examples:**

```bash
# Check for updates and install if available
conductor update
```

### conductor validate

Validate without executing:
Expand Down
158 changes: 158 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
# Release workflow for Conductor
#
# Runs on:
# - Push of v* tags (e.g., v0.1.0, v1.0.0-beta.1)
#
# Jobs:
# - lint: Runs ruff linter and formatter check
# - typecheck: Runs ty (Red Knot) type checker
# - test: Runs pytest on Python 3.12 and 3.13
# - release: Builds package and creates GitHub Release with artifacts

name: Release

on:
push:
tags:
- "v*"

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

env:
PYTHON_VERSION: "3.12"

permissions:
contents: write

jobs:
lint:
name: Lint
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Install uv
uses: astral-sh/setup-uv@v4
with:
version: "latest"
enable-cache: true

- name: Set up Python
run: uv python install ${{ env.PYTHON_VERSION }}

- name: Install dependencies
run: uv sync --group dev

- name: Run ruff linter
run: uv run ruff check src tests

- name: Run ruff formatter check
run: uv run ruff format --check src tests

typecheck:
name: Type Check
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Install uv
uses: astral-sh/setup-uv@v4
with:
version: "latest"
enable-cache: true

- name: Set up Python
run: uv python install ${{ env.PYTHON_VERSION }}

- name: Install dependencies
run: uv sync --group dev

- name: Run ty type checker
run: uv run ty check src

test:
name: Test (Python ${{ matrix.python-version }})
runs-on: ubuntu-latest
needs: [lint, typecheck]
strategy:
matrix:
python-version: ["3.12", "3.13"]
fail-fast: false

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Install uv
uses: astral-sh/setup-uv@v4
with:
version: "latest"
enable-cache: true

- name: Set up Python ${{ matrix.python-version }}
run: uv python install ${{ matrix.python-version }}

- name: Install dependencies
run: uv sync --group dev

- name: Run tests
run: uv run pytest -m "not real_api"
env:
ANTHROPIC_API_KEY: "sk-ant-test-fake-key-for-mocking"

release:
name: Create Release
runs-on: ubuntu-latest
needs: [test]

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Install uv
uses: astral-sh/setup-uv@v4
with:
version: "latest"
enable-cache: true

- name: Set up Python
run: uv python install ${{ env.PYTHON_VERSION }}

- name: Extract version from tag
id: version
run: |
VERSION="${GITHUB_REF_NAME#v}"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
# Detect pre-release: any tag with a hyphen after the version (e.g., v0.2.0-beta.1)
if [[ "$VERSION" == *-* ]]; then
echo "prerelease=true" >> "$GITHUB_OUTPUT"
else
echo "prerelease=false" >> "$GITHUB_OUTPUT"
fi

- name: Build package
run: uv build

- name: Verify package contents
run: |
ls -la dist/
uv run python -m zipfile -l dist/*.whl

- name: Create GitHub Release
env:
GH_TOKEN: ${{ github.token }}
run: |
PRERELEASE_FLAG=""
if [[ "${{ steps.version.outputs.prerelease }}" == "true" ]]; then
PRERELEASE_FLAG="--prerelease"
fi
gh release create "${{ github.ref_name }}" \
dist/* \
--generate-notes \
$PRERELEASE_FLAG
6 changes: 5 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ uv run conductor stop # auto-stop if one running, list if multi
uv run conductor stop --port 8080 # stop specific port
uv run conductor stop --all # stop all background workflows

# Update conductor
uv run conductor update # check for and install latest version

# Validate a workflow
uv run conductor validate examples/simple-qa.yaml
make validate-examples # validate all examples
Expand All @@ -52,11 +55,12 @@ make validate-examples # validate all examples

### Core Package Structure (`src/conductor/`)

- **cli/**: Typer-based CLI with commands `run`, `validate`, `init`, `templates`, `stop`
- **cli/**: Typer-based CLI with commands `run`, `validate`, `init`, `templates`, `stop`, `update`
- `app.py` - Main entry point, defines the Typer application
- `run.py` - Workflow execution command with verbose logging helpers
- `bg_runner.py` - Background process forking for `--web-bg` mode
- `pid.py` - PID file utilities for tracking/stopping background processes
- `update.py` - Update check, version comparison, and self-upgrade via `uv tool install`

- **config/**: YAML loading and Pydantic schema validation
- `schema.py` - Pydantic models for all workflow YAML structures (WorkflowConfig, AgentDef, ParallelGroup, ForEachDef, etc.)
Expand Down
129 changes: 129 additions & 0 deletions docs/projects/releases/release-management.brainstorm.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
# Plan: Auto-Update System for Conductor

## Context

Conductor is distributed as a `uv tool` installed from GitHub (`uv tool install git+https://github.com/microsoft/conductor.git`). There is currently **no mechanism** to check for updates, notify users, or upgrade. The version is hardcoded at `0.1.0` with no git tags or GitHub releases. Users who installed once will never know when improvements land.

**Goal:** Add a complete update lifecycle:
1. Tag-triggered GitHub Release workflow (à la Octane)
2. Proactive update-check on every CLI run (cached 24h)
3. `conductor update` CLI command
4. Skill awareness for Claude Code users

---

## Part 1: Tag-Triggered Release Workflow

**New file: `.github/workflows/release.yml`**

Triggered on push of tags matching `v*` (e.g., `v0.2.0`). Based on the Octane pattern:

```yaml
on:
push:
tags: ['v*']
```

The workflow will:
1. Checkout code, set up Python + uv
2. Run tests and lint (gate the release)
3. Extract version from tag (`${GITHUB_REF#refs/tags/v}`)
4. Build the package (`uv build`)
5. Generate release notes from git log since previous tag
6. Create a GitHub Release with `gh release create` + the built artifacts
7. Support prerelease tags (`v0.2.0-beta.1` → `--prerelease` flag)

**Release process for maintainers:**
```bash
# 1. Bump version in __init__.py and pyproject.toml
# 2. Commit and push
git tag v0.2.0
git push origin v0.2.0
# GitHub Actions creates the release automatically
```

---

## Part 2: Update Check System

**New file: `src/conductor/cli/update.py`**

### Functions

| Function | Description |
|----------|-------------|
| `get_cache_path()` | Returns `~/.conductor/update-check.json` |
| `read_cache()` | Read cached version info, `None` if missing or older than 24h |
| `write_cache(version, url)` | Write latest version + timestamp to cache |
| `fetch_latest_version()` | `GET api.github.com/repos/microsoft/conductor/releases/latest` via `urllib.request`, 2s timeout. Returns `(version, url)` or `None` |
| `is_newer(remote, local)` | Simple semver comparison (split on `.`, compare tuples) |
| `check_for_update_hint(console)` | Called from `main()` callback. Reads cache, fetches if stale, prints one-line hint if outdated |
| `detect_install_method()` | Parse `uv tool list` to see if installed from git or other source |
| `run_update(console)` | Execute upgrade: `uv tool install --force git+https://github.com/microsoft/conductor.git`, show before/after versions, clear cache |

### Behavior

**On every CLI run** (in the `@app.callback` `main()` function):
- Only if stderr is a TTY and not `--silent` mode
- Read cache → if fresh + version matches local → nothing
- Read cache → if fresh + version is newer → print hint:
```
💡 Conductor v0.3.0 available (you have v0.1.0). Run `conductor update` to upgrade.
```
- Cache stale/missing → fetch from GitHub API (2s timeout, fail silently) → cache → maybe print hint

**`conductor update` command:**
- Shows current version
- Fetches latest from GitHub API
- If already up to date, says so and exits
- If update available, runs `uv tool install --force git+https://github.com/microsoft/conductor.git`
- Shows before/after version
- Clears update cache

---

## Part 3: Skill + Docs Updates

Update skill and documentation to reflect the new command:
- `.claude/skills/conductor/SKILL.md` — add `conductor update` to Quick Reference
- `.claude/skills/conductor/references/execution.md` — add `conductor update` section
- `AGENTS.md` — add `update` to common commands

---

## Files to Create/Modify

| File | Action | Description |
|------|--------|-------------|
| `.github/workflows/release.yml` | **Create** | Tag-triggered release workflow
| `src/conductor/cli/update.py` | **Create** | Update check + self-update logic |
| `src/conductor/cli/app.py` | **Modify** | Add `update` command + call `check_for_update_hint()` in `main()` |
| `.claude/skills/conductor/SKILL.md` | **Modify** | Add `conductor update` to Quick Reference |
| `.claude/skills/conductor/references/execution.md` | **Modify** | Add `conductor update` docs section |
| `AGENTS.md` | **Modify** | Add update command to common commands |
| `tests/test_cli/test_update.py` | **Create** | Tests for cache logic, version comparison, hint display, update command |

### Key design decisions
- **No new dependencies** — uses `urllib.request` (stdlib) for HTTP, simple tuple comparison for semver
- **Cache at `~/.conductor/update-check.json`** — not inside the project
- **2s network timeout** — never block the user's workflow
- **TTY-only hints** — no noise in piped/scripted usage
- **`--silent` suppresses hints** — respects the existing verbosity system

---

## Verification

1. **`make test`** — all existing tests pass
2. **`make check`** — lint + typecheck pass
3. **New tests** in `tests/test_cli/test_update.py`:
- Cache read/write/expiry
- Version comparison (`is_newer`)
- Hint display with mocked fetch
- `conductor update` with mocked subprocess
4. **Manual testing:**
- `conductor update` — runs the upgrade flow
- Write a fake stale cache → run any command → see hint
- `conductor --silent run ...` → no hint
- Pipe output → no hint
5. **Release workflow:** Push a `v0.1.0` tag to a fork to verify the workflow runs
Loading
Loading