Skip to content
Open
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
45 changes: 45 additions & 0 deletions .console/backlog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,53 @@

_Durable work inventory. Update after each meaningful chunk of progress._

## Up Next

### CritiqueExecutor is not covered by the fleet-launch self-heal
- `ensure_executor_backends()` in `scripts/operations-center.sh` probes only
`import team_executor, dag_executor` and reinstalls only `../TeamExecutor` and
`../DAGExecutor`. `critique_executor` is a third backend OC loads
(`backends/critique_executor/adapter.py`), so a `uv sync` / venv-recreate that
drops it is NOT auto-repaired at fleet launch — every critique-topology task
fails at execute with `No module named 'critique_executor'` until a human notices.
- Setup (`entrypoints/setup/main.py`) now covers all three via `EXECUTOR_BACKENDS`;
the shell script is the remaining gap. Fix is to widen the probe and the sibling
loop to match, ideally sourcing the same list.
- Deferred from the 2026-08-03 setup fix to keep fleet-startup behavior out of that
change's blast radius.

## Done

### 2026-08-03: Replace setup's dead executor PATH probe with an importability check (✅ COMPLETE)
- **Objective**: `ensure_executor_installed`/`verify_executor` in
`entrypoints/setup/main.py` gated interactive setup on a `team-executor` console
script that TeamExecutor never produces (no `[project.scripts]`), so the wizard
hard-failed at that step on every run. Replace with a check of what OC actually
needs: importability of the three backends it loads as libraries.
- **Status**: ✅ COMPLETE.
- **Changes**:
- `setup/main.py` — new `EXECUTOR_BACKENDS` table, `missing_executor_backends()`
(subprocess import probe) and `ensure_executor_backends_installed()` (editable
install of `../TeamExecutor`, `../DAGExecutor`, `../CritiqueExecutor` + re-probe),
mirroring `ensure_executor_backends()` in `scripts/operations-center.sh`.
Removed `ensure_executor_installed`, `verify_executor`, the "Executor binary"
prompt, and `SetupAnswers.executor_binary`.
- `maintenance/dependency_check.py` — same stale-CLI bug: `team-executor --version`
replaced with `executor_backend_status()` (importability + distribution version);
`kind` `"cli"` → `"library"`.
- Docs — `docs/operator/setup.md` "Executor Install Behavior" + Executor/Advanced Mode
bullets rewritten; `docs/demo.md` PATH prerequisite corrected.
- **Config-key decisions**: `team_executor.binary` removed (no writer, no settings
field, no reader outside setup's own prompt default). `OPERATIONS_CENTER_EXECUTOR_INSTALL_REF`
kept but repurposed as a drift-reporting version pin — it still has a live consumer
in `dependency_check.py`, but nothing installs from it anymore.
- **Verification**: probed the live WSL2 venv — `missing_executor_backends()` returns
`[]`, all three backends report `(True, '0.1.0')`, `shutil.which("team-executor")`
is `None` (confirming the old gate could never pass). 10 new tests; 26 pass across
the two touched test files; `ruff check`/`ruff format --check` clean. Full suite:
10354 passed, 6 failed — the same pre-existing sandbox/timing failures as prior
stages, all reproduced on an unmodified checkout.

### 2026-07-15: Stage 4 — Refactor existing code to use the new shared helper (✅ COMPLETE)
- **Objective**: Independently re-verify Stage 2's migration against the "refactor existing
code" acceptance bar (identified/updated all relevant callsites, replaced redundant
Expand Down
62 changes: 62 additions & 0 deletions .console/log.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,65 @@
## 2026-08-03 — fix(setup): replace the dead executor PATH probe with an importability check

`entrypoints/setup/main.py` gated the whole wizard on a step that could never
pass: `ensure_executor_installed("team-executor")` shelled out to `uv tool
install git+.../TeamExecutor@dev --force`, then re-checked PATH and raised
`[executor] ERROR: installation failed` if the binary still wasn't there —
followed by `verify_executor` running `team-executor --help`. TeamExecutor
declares no `[project.scripts]`, so no `team-executor` console script is ever
produced. Verified against the live WSL2 stack: `shutil.which("team-executor")`
is `None`. Every interactive setup run therefore hard-failed at that gate, after
the uv install had already burned a network fetch.

The probe was measuring the wrong thing. OC consumes all three execute backends
as LIBRARIES — `backends/{team_executor,dag_executor,critique_executor}/adapter.py`
each do a plain `import <module>` — so importability in OC's venv is the only
readiness signal that means anything. PATH is not: TeamExecutor and
CritiqueExecutor ship no console script at all, and the one that exists
(DAGExecutor's `dag-executor`) is never invoked by OC.

Replaced with `missing_executor_backends()` + `ensure_executor_backends_installed()`,
mirroring the `ensure_executor_backends()` self-heal in `scripts/operations-center.sh`:
probe each backend with `<venv-python> -c "import <module>"`, and for anything
missing install the sibling checkout editable (`../TeamExecutor`, `../DAGExecutor`,
`../CritiqueExecutor`), then re-probe. Setup now covers all THREE backends; the
shell self-heal still only covers two (`team_executor`, `dag_executor`) — a
CritiqueExecutor drop mid-life is not yet auto-repaired at fleet launch. Left
alone deliberately (fleet-startup behavior, out of this change's blast radius);
flagged for follow-up. The probe runs in a subprocess, not via importlib in-process,
so an install that lands partway through setup is visible to the re-check.

Config-key decisions:

* `team_executor.binary` — REMOVED. It had no consumer in either direction:
`TeamExecutorSettings` has no `binary` field, `render_settings_yaml` never
wrote the key, and the only reader was setup's own prompt default. Dropped the
prompt and the `SetupAnswers.executor_binary` field.
* `OPERATIONS_CENTER_EXECUTOR_INSTALL_REF` — KEPT, repurposed. It does have a
live consumer (`entrypoints/maintenance/dependency_check.py`), but its old
meaning ("git ref to install from") died with `ensure_executor_installed`.
Relabeled as a version pin for drift reporting, which is what dependency-check
actually does with it and how the docs already grouped it (alongside the Plane
and provider CLI pins).

Same stale-CLI bug had a second instance: `collect_dependency_statuses` probed
`team-executor --version`, so the TeamExecutor row reported
`healthy=False` / "not installed or not on PATH" on every single run, forever.
Replaced with `executor_backend_status()` (importability + best-effort
distribution version via `packages_distributions()`); `kind` corrected
`"cli"` → `"library"`. Verified against the live WSL2 venv: all three backends
report `(True, '0.1.0')` — the editable-install version lookup resolves.

Tests: 7 new in `test_setup_cli.py` (probe call shape, no-op when all
importable, editable install of missing siblings, missing-checkout error,
install-failure error, still-unimportable-after-install error, backend-list pin)
and 3 in `test_dependency_check.py`. 26 pass in the two touched files; full suite
10354 passed with the same 6 pre-existing sandbox/timing failures as prior
stages (reproduced on an unmodified checkout — none related).

Docs: rewrote `docs/operator/setup.md` "Executor Install Behavior" to describe
the import-based flow, fixed the "install/verify `team-executor` CLI" bullet and
the Advanced Mode pin description, and corrected the `docs/demo.md` prerequisite
that told operators to put `team-executor` on PATH.
## 2026-08-03 — fix(hooks): pre-push resolved the wrong workspace root inside a git worktree

`.hooks/pre-push` locates the boundary disclosure artifact by globbing sibling
Expand Down
3 changes: 2 additions & 1 deletion docs/demo.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,8 @@ Full end-to-end walkthrough from local startup to a completed task with retained
- Python 3.11+
- A GitHub account with a repo and a personal access token (repo scope)
- `gh` CLI authenticated (`gh auth login`) or a `GITHUB_TOKEN` PAT
- TeamExecutor (`team-executor`) installed and accessible via PATH
- TeamExecutor, DAGExecutor, and CritiqueExecutor cloned as siblings of this repo
(setup installs them editable into the OC venv — they are imported, not run from PATH)

### Step 1 — First-time setup

Expand Down
31 changes: 23 additions & 8 deletions docs/operator/setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ source .env.operations-center.local
TeamExecutor is the multi-agent coding engine OperationsCenter uses for task execution.
See `src/operations_center/backends/team_executor/` for the adapter implementation.

- install/verify `team-executor` CLI
- verify the execute backends are importable, installing missing sibling checkouts editable
- configure orchestrator defaults
- persist local execution settings

Expand Down Expand Up @@ -111,24 +111,39 @@ so they work regardless of which venv was activated during bootstrap.

## Executor Install Behavior

OperationsCenter loads its execute backends as **libraries**, not CLIs — the adapters in
`src/operations_center/backends/<name>/` do a plain `import team_executor` / `import dag_executor` /
`import critique_executor`. So readiness means "importable in the OC venv", not "on `PATH`".
None of the three ships a console script OC invokes.

The backends are sibling *checkouts*, not declared OC dependencies: `uv pip install -e .[dev]`
never installs them, and a `uv sync` or venv recreate actively drops them.

Setup:

- checks whether `team-executor` is on `PATH`
- installs `uv` if needed
- installs TeamExecutor if missing
- verifies the install with `team-executor --help`
- probes each backend with `<oc-venv-python> -c "import <module>"`
- installs `uv` if needed, and only if a backend is actually missing
- installs the missing backend editable from its sibling checkout
(`../TeamExecutor`, `../DAGExecutor`, `../CritiqueExecutor`)
- fails with the expected checkout path if a sibling is not cloned next to this repo
- re-probes after installing and fails if a backend is still not importable

Setup is intended to be idempotent: it does not reinstall the executor when the current install already works.
Setup is idempotent: the import probe is cheap and the install only fires for backends that
are actually missing. `scripts/operations-center.sh` runs the same self-heal
(`ensure_executor_backends`) at every fleet launch, so a mid-life drop recovers on the next start.

## Advanced Mode

Advanced mode also exposes optional version pins for:

- Plane
- TeamExecutor
- TeamExecutor (`OPERATIONS_CENTER_EXECUTOR_INSTALL_REF`)
- supported provider CLIs

Pins are for reproducible local installs. They do not automatically trigger update checks during normal runs.
Pins record the version this machine is expected to run. They do not automatically trigger update
checks during normal runs, and the TeamExecutor pin does not drive an install — the backend comes
from the sibling checkout. `dependency-check` compares each pin against what is installed and
against the upstream latest release, and reports the drift.

## Per-Repo Reviewer Settings

Expand Down
45 changes: 33 additions & 12 deletions src/operations_center/entrypoints/maintenance/dependency_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
from __future__ import annotations

import argparse
import importlib.metadata
import importlib.util
import json
import os
import re
import subprocess
import uuid
from dataclasses import asdict, dataclass
from pathlib import Path
Expand Down Expand Up @@ -80,6 +81,31 @@ def plane_latest_from_env(env: dict[str, str]) -> tuple[str | None, str | None]:
return pinned, setup_url


def executor_backend_status(module: str) -> tuple[bool, str | None]:
"""Return ``(importable, distribution version)`` for an execute backend module.

OC loads TeamExecutor as a LIBRARY (``backends/team_executor/adapter.py``
imports it directly), and TeamExecutor declares no ``[project.scripts]`` — so
importability, not PATH, is what "installed" means here. The version is
best-effort: an editable sibling checkout whose metadata does not map the
top-level module back to a distribution reports importable with no version.
"""
try:
importable = importlib.util.find_spec(module) is not None
except (ImportError, ValueError):
importable = False
if not importable:
return False, None
candidates = list(importlib.metadata.packages_distributions().get(module, ()))
candidates.append(module.replace("_", "-"))
for distribution in candidates:
try:
return True, normalize_version(importlib.metadata.version(distribution))
except importlib.metadata.PackageNotFoundError:
continue
return True, None


def current_plane_health(settings: Settings) -> bool:
try:
response = httpx.get(settings.plane.base_url, timeout=10.0)
Expand Down Expand Up @@ -114,20 +140,15 @@ def collect_dependency_statuses(settings: Settings, env: dict[str, str]) -> list
)
)

try:
proc = subprocess.run(
["team-executor", "--version"], check=False, capture_output=True, text=True, timeout=10
)
executor_version_raw = (proc.stdout or proc.stderr).strip() if proc.returncode == 0 else ""
except Exception:
executor_version_raw = ""
executor_installed = bool(executor_version_raw)
executor_installed_version = normalize_version(executor_version_raw)
executor_installed, executor_installed_version = executor_backend_status("team_executor")
executor_pinned = normalize_version(env.get("OPERATIONS_CENTER_EXECUTOR_INSTALL_REF"))
executor_latest = fetch_github_latest_release("ProtocolWarden", "TeamExecutor")
executor_notes: list[str] = []
if not executor_installed:
executor_notes.append("team-executor is not installed or not on PATH.")
executor_notes.append(
"team_executor is not importable. Run `./scripts/operations-center.sh setup` or "
"install the sibling TeamExecutor checkout editable into the OC venv."
)
if (
executor_pinned
and executor_installed_version
Expand All @@ -144,7 +165,7 @@ def collect_dependency_statuses(settings: Settings, env: dict[str, str]) -> list
DependencyStatus(
key="team_executor",
label="TeamExecutor",
kind="cli",
kind="library",
installed_version=executor_installed_version,
pinned_version=executor_pinned,
upstream_latest=executor_latest,
Expand Down
Loading
Loading