Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 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
40 changes: 40 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,46 @@ jobs:
- name: Pytest
run: python -m pytest tests -q --tb=short

# Enumerate Olive pass registry only — no models, no optimization, no CUDA.
# Pin matches src/lib/passCatalog.ts OLIVE_VERSION and matrix olive_version_support.max.
olive-pass-availability:
runs-on: ubuntu-latest
permissions:
contents: read
timeout-minutes: 15
defaults:
run:
working-directory: olive-mcp-server
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false

- uses: actions/setup-python@v7
with:
python-version: "3.12"
cache: pip
cache-dependency-path: olive-mcp-server/pyproject.toml

- name: Install pinned Olive (CPU registry only)
env:
# Discourage CUDA wheel selection for transitive torch/ort if resolved.
CUDA_VISIBLE_DEVICES: ""
PIP_NO_CACHE_DIR: "1"
run: |
set -euo pipefail
# Documented pin: Olive 0.12.1 (passCatalog OLIVE_VERSION / matrix max).
# olive-mcp-server does not depend on olive-ai; pin lives here for CI.
pip install "olive-ai==0.12.1"

- name: Enumerate registry vs compatibility matrix
env:
CUDA_VISIBLE_DEVICES: ""
run: |
set -euo pipefail
python scripts/check_olive_pass_availability.py \
olive_mcp_server/knowledge_base/compatibility_matrix.json

docker-build:
runs-on: ubuntu-latest
permissions:
Expand Down
275 changes: 224 additions & 51 deletions .github/workflows/kb-update.yml
Original file line number Diff line number Diff line change
@@ -1,51 +1,224 @@
name: KB Auto-Update

on:
schedule:
- cron: "0 6 * * 1" # Monday 06:00 UTC
workflow_dispatch: {}

concurrency:
group: kb-update
cancel-in-progress: false

jobs:
update:
runs-on: ubuntu-latest
timeout-minutes: 5
defaults:
run:
working-directory: olive-mcp-server
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false

- uses: actions/setup-python@v7
with:
python-version: "3.12"
cache: pip
cache-dependency-path: olive-mcp-server/pyproject.toml

- name: Install dependencies
run: pip install -e . "mcp<2"

- name: Run KB update
run: python scripts/update_kb.py

- name: Check PyPI for new olive-ai versions
run: |
LATEST=$(python -c "import urllib.request, json; data = json.loads(urllib.request.urlopen('https://pypi.org/pypi/olive-ai/json', timeout=15).read()); print(data['info']['version'])")
echo "Latest olive-ai on PyPI: $LATEST"
echo "olive_latest_version=$LATEST" >> "$GITHUB_OUTPUT"
id: pypi

- name: Upload report
if: ${{ !cancelled() }}
uses: actions/upload-artifact@v7
with:
name: kb-update-report
path: |
olive-mcp-server/olive_mcp_server/knowledge_base/update_report.json
olive-mcp-server/olive_mcp_server/knowledge_base/candidate_quirks.json
retention-days: 30
name: KB Auto-Update

on:
schedule:
- cron: "0 6 * * 1" # Monday 06:00 UTC
workflow_dispatch: {}

concurrency:
group: kb-update
cancel-in-progress: false

# Job-level permissions below are least-privilege for PR creation.
permissions: {}

jobs:
update:
runs-on: ubuntu-latest
timeout-minutes: 20
permissions:
contents: write # push chore/kb-refresh branch only
pull-requests: write # open/update one labeled refresh PR
defaults:
run:
working-directory: olive-mcp-server
steps:
- uses: actions/checkout@v7
with:
# Credentials required so the refresh-PR step can push the bot branch.
persist-credentials: true
token: ${{ secrets.GITHUB_TOKEN }}

- uses: actions/setup-python@v7
with:
python-version: "3.12"
cache: pip
cache-dependency-path: olive-mcp-server/pyproject.toml

- name: Install dependencies
run: pip install -e ".[dev]" "mcp<2"

- name: Run KB update
run: python scripts/update_kb.py

- name: Run KB expand
run: python scripts/expand_kb.py

- name: Compatibility matrix validation
run: |
set -euo pipefail
if [ -f tests/test_compatibility_matrix.py ]; then
python -m pytest tests/test_compatibility_matrix.py -q --tb=short
else
echo "tests/test_compatibility_matrix.py not found; running lightweight matrix sanity checks"
python -c "from pathlib import Path; import json; kb=Path('olive_mcp_server/knowledge_base'); matrix=json.loads((kb/'compatibility_matrix.json').read_text(encoding='utf-8')); passes_doc=json.loads((kb/'passes.json').read_text(encoding='utf-8')); pass_names={p['name'] for p in passes_doc.get('passes', []) if isinstance(p, dict) and isinstance(p.get('name'), str)}; assert pass_names, 'passes.json has no passes'; models=matrix.get('models'); assert isinstance(models, list) and models, 'compatibility_matrix.json has no models'; print(f'OK: {len(models)} models, {len(pass_names)} canonical passes')"
fi

- name: Python MCP tests
run: python -m pytest tests -q --tb=short

- name: Check PyPI for new olive-ai versions
id: pypi
run: |
LATEST=$(python -c "import urllib.request, json; data = json.loads(urllib.request.urlopen('https://pypi.org/pypi/olive-ai/json', timeout=15).read()); print(data['info']['version'])")
echo "Latest olive-ai on PyPI: $LATEST"
echo "olive_latest_version=$LATEST" >> "$GITHUB_OUTPUT"

- name: Upload report
if: ${{ !cancelled() }}
uses: actions/upload-artifact@v7
with:
name: kb-update-report
path: |
olive-mcp-server/olive_mcp_server/knowledge_base/update_report.json
olive-mcp-server/olive_mcp_server/knowledge_base/candidate_quirks.json
olive-mcp-server/olive_mcp_server/knowledge_base/refresh_metadata.json
if-no-files-found: warn
retention-days: 30

- name: Detect KB output changes
id: kb_changes
working-directory: ${{ github.workspace }}
run: |
set -euo pipefail
KB_DIR="olive-mcp-server/olive_mcp_server/knowledge_base"
CHANGED_TRACKED="$(git diff --name-only HEAD -- "$KB_DIR" || true)"
CHANGED_UNTRACKED="$(git ls-files --others --exclude-standard -- "$KB_DIR" || true)"
if [ -z "${CHANGED_TRACKED}" ] && [ -z "${CHANGED_UNTRACKED}" ]; then
echo "has_changes=false" >> "$GITHUB_OUTPUT"
echo "No tracked KB output changes (clean generated diff — no PR)"
else
echo "has_changes=true" >> "$GITHUB_OUTPUT"
echo "KB paths changed:"
printf '%s\n' "${CHANGED_TRACKED}" "${CHANGED_UNTRACKED}" | sed '/^$/d'
fi

# Creates or updates at most one PR (head: chore/kb-refresh, label: kb-refresh).
# Never auto-merges. Skips force-updating when the branch has human commits.
# Does not rewrite an existing PR title/body (gh pr create only when absent).
- name: Create or update labeled refresh PR
if: steps.kb_changes.outputs.has_changes == 'true'
working-directory: ${{ github.workspace }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BRANCH: chore/kb-refresh
LABEL: kb-refresh
BOT_EMAIL: 41898282+github-actions[bot]@users.noreply.github.com
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
OLIVE_PYPI: ${{ steps.pypi.outputs.olive_latest_version }}
run: |
set -euo pipefail
KB_DIR="olive-mcp-server/olive_mcp_server/knowledge_base"
DEFAULT_BRANCH="${DEFAULT_BRANCH:-main}"

git config user.name "github-actions[bot]"
git config user.email "${BOT_EMAIL}"

# Idempotent label for the single refresh PR.
gh label create "${LABEL}" \
--description "Automated MCP knowledge-base refresh (review required; never auto-merged)" \
--color "0E8A16" \
--force

# Preserve human edits: refuse to rebuild/force-push if non-bot commits exist.
if git ls-remote --exit-code origin "refs/heads/${BRANCH}" >/dev/null 2>&1; then
# Fetch the full refresh branch tip so human commits outside a shallow
# window are still visible, then deepen default-branch history until
# a merge-base exists (avoids false "human edits" from disconnected tips).
git fetch origin "${BRANCH}"
git fetch origin "${DEFAULT_BRANCH}" --depth=50
for _ in 1 2 3 4 5 6 7 8; do
if git merge-base "origin/${DEFAULT_BRANCH}" "origin/${BRANCH}" >/dev/null 2>&1; then
break
fi
git fetch origin "${DEFAULT_BRANCH}" --deepen=200 || true
done
if ! git merge-base "origin/${DEFAULT_BRANCH}" "origin/${BRANCH}" >/dev/null 2>&1; then
git fetch origin "${DEFAULT_BRANCH}" --unshallow 2>/dev/null \
|| git fetch origin "${DEFAULT_BRANCH}"
fi
HUMAN="$(
git log --format='%ae' "origin/${DEFAULT_BRANCH}..origin/${BRANCH}" 2>/dev/null \
| grep -vF "${BOT_EMAIL}" \
| grep -v '^$' \
|| true
)"
if [ -n "${HUMAN}" ]; then
echo "Refresh branch has human commits; skipping automatic update."
PR_NUM="$(gh pr list --head "${BRANCH}" --state open --json number --jq '.[0].number // empty')"
if [ -n "${PR_NUM}" ]; then
BODY_FILE="$(mktemp)"
{
echo "⚠️ Scheduled **KB Auto-Update** found new generated knowledge-base changes, but \`${BRANCH}\` contains human commits."
echo ""
echo "Automatic branch update was **skipped** so your edits are preserved."
echo ""
echo "Please merge or close PR #${PR_NUM}, then re-run [this workflow run](${RUN_URL})."
} >"${BODY_FILE}"
gh pr comment "${PR_NUM}" --body-file "${BODY_FILE}"
rm -f "${BODY_FILE}"
fi
exit 0
fi
fi

# Snapshot generated KB outputs, then rebuild branch from default tip.
# -f discards the dirty generator worktree after the snapshot copy.
TMP_KB="$(mktemp -d)"
cp -a "${KB_DIR}/." "${TMP_KB}/"

git fetch origin "${DEFAULT_BRANCH}"
git checkout -f -B "${BRANCH}" "origin/${DEFAULT_BRANCH}"

mkdir -p "${KB_DIR}"
cp -a "${TMP_KB}/." "${KB_DIR}/"
rm -rf "${TMP_KB}"

git add -- "${KB_DIR}"
if git diff --cached --quiet; then
echo "No commit-able KB changes after rebasing onto ${DEFAULT_BRANCH}; skipping PR"
exit 0
fi

git commit -m "chore(kb): refresh knowledge base"
# Bot-owned branch: safe to replace tip when only bot commits were present.
git push -u origin "HEAD:refs/heads/${BRANCH}" --force-with-lease

EXISTING="$(gh pr list --head "${BRANCH}" --state open --json number --jq '.[0].number // empty')"
if [ -n "${EXISTING}" ]; then
echo "Updated existing refresh PR #${EXISTING} (title/body left unchanged; not merged)"
exit 0
fi

BODY_FILE="$(mktemp)"
{
echo "## Automated knowledge base refresh"
echo ""
echo "Opened by the **KB Auto-Update** workflow (Monday 06:00 UTC schedule or \`workflow_dispatch\`)."
echo ""
echo "**Not auto-merged** — human review is required before merge."
echo ""
echo "### Review checklist"
echo "- [ ] Diff is intentional (no noisy timestamp-only churn)"
echo "- [ ] \`refresh_metadata.json\` provenance looks correct"
echo "- [ ] Compatibility validation and Python MCP tests passed on the workflow run"
echo "- [ ] Candidate quirks reviewed when present"
echo ""
echo "### Provenance"
echo "- Workflow run: ${RUN_URL}"
echo "- Artifact: \`kb-update-report\` (update report, candidate quirks, refresh metadata)"
echo "- Latest \`olive-ai\` on PyPI (informational): ${OLIVE_PYPI:-unknown}"
echo ""
echo "### Policy"
echo "- At most **one** open PR uses head branch \`${BRANCH}\` and label \`${LABEL}\`."
echo "- Subsequent scheduled runs update this branch only when generated KB outputs change."
echo "- Human commits on \`${BRANCH}\` block automatic updates until the PR is merged or closed."
} >"${BODY_FILE}"
gh pr create \
--base "${DEFAULT_BRANCH}" \
--head "${BRANCH}" \
--title "chore(kb): scheduled knowledge base refresh" \
--label "${LABEL}" \
--body-file "${BODY_FILE}"
rm -f "${BODY_FILE}"
20 changes: 16 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,14 +217,26 @@ Olive Studio validates at several layers so bad recipes fail fast:

## MCP integration

The repository includes `olive-mcp-server/`, a Python MCP server that exposes Olive pass, hardware, troubleshooting, and compatibility tools.
The repository includes `olive-mcp-server/`, a Python MCP server that exposes Olive pass, hardware, troubleshooting, compatibility, UIState recipe bridge, and local feedback tools.

- `.mcp.json` wires the server to Claude via `olive-mcp-server/run.py`.
- `server.ts` exposes `POST /api/mcp/tool` so the web UI can proxy tool calls.
- `server.ts` exposes `POST /api/mcp/tool` so the web UI can proxy tool calls, and a loopback-only `POST /api/mcp/studio-recipe` bridge for UIState validation/recipe build (no Olive execution).
- Bridge-backed MCP tools (`validate_ui_state_recipe`, `get_recipe_for_ui_state`) require Studio running and `OLIVE_STUDIO_API_URL` set to a **loopback** base URL (e.g. `http://127.0.0.1:3000`).
- **Assistant chat** (`POST /api/ai/chat`) queries Olive MCP first (docs search + targeted tools) and injects those excerpts as the primary system knowledge. Optional external web search runs only when MCP coverage is thin and `OLIVE_STUDIO_WEB_SEARCH_URL` is set.
- `MCPDiagnosticCard` renders MCP troubleshooting results inside the recipe workspace.
- `MCPDiagnosticCard` renders MCP troubleshooting results inside the recipe workspace, with optional thumbs feedback when a diagnosis has a stable `matched_entry`.

See [olive-mcp-server/README.md](olive-mcp-server/README.md) for setup and tool details.
### Local-only diagnostic feedback (privacy)

- Thumbs up/down on a matched MCP diagnosis call `record_troubleshoot_feedback` through the existing MCP proxy.
- Feedback is **aggregate-only** (entry id + rating counts, optional allowlisted reason codes). Error logs, stack traces, and free-form notes are never accepted or stored.
- Data stays on the local machine under the MCP user-data path (override with `OLIVE_MCP_FEEDBACK_PATH`). Nothing is uploaded by this path.
- Ranking influence is capped so feedback only nudges close ties.

### Knowledge-base refresh PRs

Scheduled workflow [`.github/workflows/kb-update.yml`](.github/workflows/kb-update.yml) (Monday + manual) runs the KB generators, compatibility checks, and Python MCP tests. When generated KB files change, it opens or updates a single labeled PR (`kb-refresh`) for human review. **PRs are never auto-merged.**

See [olive-mcp-server/README.md](olive-mcp-server/README.md) for setup, bridge preconditions, tool input/response examples, and feedback details.

---

Expand Down
Loading
Loading