Skip to content

Commit c8d578e

Browse files
Centralize Harbor pin and sync backend workers to dsh revision
Add oddish/src/oddish/harbor-pin.toml as the single source of truth for the locked abundant-ai/harbor revision, load it from config at runtime, and add a sync script plus CI guard so backend/pyproject.toml cannot drift from oddish again. Bump production workers to 078136c5, which includes the dsh agent. Co-authored-by: rishi <rishi@abundant.ai>
1 parent b9b2582 commit c8d578e

9 files changed

Lines changed: 190 additions & 20 deletions

File tree

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
name: Harbor Pin Sync Guard
2+
3+
# Enforces oddish/src/oddish/harbor-pin.toml as the single source of truth for
4+
# the locked abundant-ai/harbor revision. Both pyproject.toml files must stay
5+
# in sync because the backend worker image cannot inherit uv sources from the
6+
# transitive oddish dependency.
7+
8+
on:
9+
pull_request:
10+
paths:
11+
- "oddish/src/oddish/harbor-pin.toml"
12+
- "oddish/pyproject.toml"
13+
- "backend/pyproject.toml"
14+
- "oddish/scripts/sync_harbor_pin.py"
15+
- ".github/workflows/harbor-pin-guard.yml"
16+
push:
17+
branches:
18+
- main
19+
- staging
20+
paths:
21+
- "oddish/src/oddish/harbor-pin.toml"
22+
- "oddish/pyproject.toml"
23+
- "backend/pyproject.toml"
24+
- "oddish/scripts/sync_harbor_pin.py"
25+
- ".github/workflows/harbor-pin-guard.yml"
26+
27+
jobs:
28+
check:
29+
runs-on: ubuntu-latest
30+
defaults:
31+
run:
32+
working-directory: oddish
33+
steps:
34+
- uses: actions/checkout@v5
35+
- uses: astral-sh/setup-uv@v6
36+
with:
37+
enable-cache: true
38+
cache-dependency-glob: |
39+
oddish/uv.lock
40+
backend/uv.lock
41+
- name: Install sync script deps
42+
run: uv sync --extra dev
43+
- name: Assert pyproject harbor pins match harbor-pin.toml
44+
run: uv run python scripts/sync_harbor_pin.py --check

backend/pyproject.toml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,8 @@ dev = [
6161

6262
[tool.uv.sources]
6363
oddish = { path = "../oddish", editable = true }
64-
# Mirror Oddish's Harbor source. The backend builds the worker image, so it
65-
# cannot inherit this source from the transitive Oddish dependency.
66-
harbor = { git = "https://github.com/abundant-ai/harbor", rev = "ca4fda6aa75180487c2c7c07fabaaf03d01b2e8d" }
64+
# Mirror oddish/src/oddish/harbor-pin.toml (run oddish/scripts/sync_harbor_pin.py).
65+
harbor = { git = "https://github.com/abundant-ai/harbor", rev = "078136c5c9ca03f8498babe6ea372f39f9062025" }
6766

6867
[tool.uv]
6968
override-dependencies = ["harbor==0.16.1"]

backend/uv.lock

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

oddish/pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ oddish = "oddish.cli:app"
116116
oddish-docstore-mcp = "oddish.mcp.docstore_server:main"
117117

118118
[tool.uv.sources]
119-
# Keep the worker image on the exact Harbor revision used by Oddish's runtime gate.
119+
# Mirror src/oddish/harbor-pin.toml (run scripts/sync_harbor_pin.py after edits).
120120
harbor = { git = "https://github.com/abundant-ai/harbor", rev = "078136c5c9ca03f8498babe6ea372f39f9062025" }
121121

122122
[tool.uv]
@@ -127,7 +127,7 @@ exclude = [".git", "uv.lock"]
127127

128128
[tool.hatch.build.targets.wheel]
129129
packages = ["src/oddish"]
130-
include = ["src/oddish/analyze/*.txt", "src/oddish/analyze/prompts/*.txt", "src/oddish/assets/oddish-query", "src/oddish/seeds/**", "src/oddish/evals/analyzer/prompts/*.txt"]
130+
include = ["src/oddish/analyze/*.txt", "src/oddish/analyze/prompts/*.txt", "src/oddish/assets/oddish-query", "src/oddish/harbor-pin.toml", "src/oddish/seeds/**", "src/oddish/evals/analyzer/prompts/*.txt"]
131131

132132
[tool.mypy]
133133
python_version = "3.13"

oddish/scripts/sync_harbor_pin.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
#!/usr/bin/env python3
2+
"""Sync oddish/src/oddish/harbor-pin.toml into both pyproject harbor pins.
3+
4+
The TOML file is the single source of truth. Both oddish/pyproject.toml and
5+
backend/pyproject.toml duplicate the pin because uv cannot inherit
6+
[tool.uv.sources] across the backend worker image boundary.
7+
"""
8+
9+
from __future__ import annotations
10+
11+
import argparse
12+
import sys
13+
import tomllib
14+
from pathlib import Path
15+
16+
import tomlkit
17+
18+
_ODDISH_ROOT = Path(__file__).resolve().parents[1]
19+
_REPO_ROOT = _ODDISH_ROOT.parent
20+
_PIN_FILE = _ODDISH_ROOT / "src/oddish/harbor-pin.toml"
21+
_TARGETS = (
22+
_ODDISH_ROOT / "pyproject.toml",
23+
_REPO_ROOT / "backend" / "pyproject.toml",
24+
)
25+
26+
27+
def _load_pin() -> dict[str, str]:
28+
with _PIN_FILE.open("rb") as fh:
29+
raw = tomllib.load(fh)
30+
git = raw.get("git")
31+
rev = raw.get("rev")
32+
if not isinstance(git, str) or not isinstance(rev, str):
33+
raise SystemExit(f"invalid harbor pin in {_PIN_FILE}")
34+
return {"git": git, "rev": rev}
35+
36+
37+
def _sync_file(path: Path, pin: dict[str, str], *, check: bool) -> bool:
38+
text = path.read_text(encoding="utf-8")
39+
doc = tomlkit.parse(text)
40+
sources = doc.get("tool", {}).get("uv", {}).get("sources")
41+
if not isinstance(sources, dict) or "harbor" not in sources:
42+
raise SystemExit(f"missing [tool.uv.sources].harbor in {path}")
43+
harbor = sources["harbor"]
44+
if not isinstance(harbor, dict):
45+
raise SystemExit(f"invalid [tool.uv.sources].harbor in {path}")
46+
47+
changed = harbor.get("git") != pin["git"] or harbor.get("rev") != pin["rev"]
48+
if check:
49+
return changed
50+
51+
harbor["git"] = pin["git"]
52+
harbor["rev"] = pin["rev"]
53+
path.write_text(tomlkit.dumps(doc), encoding="utf-8")
54+
return changed
55+
56+
57+
def main() -> int:
58+
parser = argparse.ArgumentParser(description=__doc__)
59+
parser.add_argument(
60+
"--check",
61+
action="store_true",
62+
help="exit 1 when any pyproject pin drifts from harbor-pin.toml",
63+
)
64+
args = parser.parse_args()
65+
66+
pin = _load_pin()
67+
drifted: list[Path] = []
68+
for target in _TARGETS:
69+
if _sync_file(target, pin, check=args.check):
70+
drifted.append(target)
71+
72+
if args.check:
73+
if drifted:
74+
rel = ", ".join(str(p.relative_to(_REPO_ROOT)) for p in drifted)
75+
print(
76+
"harbor pin drift: run `cd oddish && uv run python scripts/sync_harbor_pin.py` "
77+
f"to sync {rel} with src/oddish/harbor-pin.toml",
78+
file=sys.stderr,
79+
)
80+
return 1
81+
print("harbor pin sync: OK")
82+
return 0
83+
84+
if drifted:
85+
rel = ", ".join(str(p.relative_to(_REPO_ROOT)) for p in drifted)
86+
print(f"updated harbor pin in {rel}")
87+
else:
88+
print("harbor pin already in sync")
89+
return 0
90+
91+
92+
if __name__ == "__main__":
93+
raise SystemExit(main())

oddish/src/oddish/config.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
from harbor.models.agent.name import AgentName
1515
from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider
1616

17+
from oddish.harbor_pin import load_harbor_pin as _load_harbor_pin
18+
1719
logger = logging.getLogger(__name__)
1820

1921

@@ -145,16 +147,15 @@ def nop_oracle_kind(agent: str | None) -> str | None:
145147

146148

147149
# --- Configurable Harbor source ----------------------------------------------
148-
# The locked default fork + commit. HARBOR_DEFAULT_SHA MUST equal the pin in
149-
# both uv.lock files (a test asserts it against oddish/uv.lock). This is the
150-
# lean Harbor baked into the default Modal/Daytona worker image; GKE (TPU)
151-
# trials run a heavier GKE-enabled Harbor on a dedicated blessed-variant image
152-
# (see HARBOR_VARIANTS in oddish.core.harbor_source), never this default.
153-
HARBOR_DEFAULT_SOURCE = "https://github.com/abundant-ai/harbor"
154-
# Exact abundant-ai/harbor revision resolved into both uv.lock files. Harbor
155-
# PR #24 recovers Claude Code ATIF from the streamed transcript after timeouts,
156-
# on top of PR #25's subagent attribution and PR #26's lifecycle setup hooks.
157-
HARBOR_DEFAULT_SHA = "ca4fda6aa75180487c2c7c07fabaaf03d01b2e8d"
150+
# The locked default fork + commit lives in src/oddish/harbor-pin.toml (single
151+
# source of truth). HARBOR_DEFAULT_SHA MUST equal the pin in both uv.lock files
152+
# (a test asserts it against oddish/uv.lock). This is the lean Harbor baked
153+
# into the default Modal/Daytona worker image; GKE (TPU) trials run a heavier
154+
# GKE-enabled Harbor on a dedicated blessed-variant image (see HARBOR_VARIANTS
155+
# in oddish.core.harbor_source), never this default.
156+
_harbor_pin = _load_harbor_pin()
157+
HARBOR_DEFAULT_SOURCE = _harbor_pin["git"]
158+
HARBOR_DEFAULT_SHA = _harbor_pin["rev"]
158159

159160
_HARBOR_URL_PREFIXES = ("git+", "http://", "https://", "ssh://")
160161

oddish/src/oddish/harbor-pin.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Locked abundant-ai/harbor revision for default-variant worker images and
2+
# trial provenance stamps. Edit this file, then run:
3+
# cd oddish && uv run python scripts/sync_harbor_pin.py
4+
# and regenerate both oddish/uv.lock and backend/uv.lock.
5+
git = "https://github.com/abundant-ai/harbor"
6+
rev = "078136c5c9ca03f8498babe6ea372f39f9062025"

oddish/src/oddish/harbor_pin.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
"""Load the locked default Harbor git pin shipped with the oddish package."""
2+
3+
from __future__ import annotations
4+
5+
import tomllib
6+
from functools import lru_cache
7+
from pathlib import Path
8+
9+
_PIN_FILE = Path(__file__).resolve().with_name("harbor-pin.toml")
10+
11+
12+
@lru_cache(maxsize=1)
13+
def load_harbor_pin() -> dict[str, str]:
14+
with _PIN_FILE.open("rb") as fh:
15+
raw = tomllib.load(fh)
16+
git = raw.get("git")
17+
rev = raw.get("rev")
18+
if not isinstance(git, str) or not isinstance(rev, str):
19+
raise ValueError(f"invalid harbor pin in {_PIN_FILE}")
20+
return {"git": git, "rev": rev}

oddish/tests/test_harbor_spec_parse.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,13 @@ def test_default_sha_matches_uv_lock_pin():
6262
), "HARBOR_DEFAULT_SHA drifted from backend/uv.lock"
6363

6464

65+
def test_harbor_pin_toml_matches_default():
66+
with open("src/oddish/harbor-pin.toml", "rb") as fh:
67+
pin = tomllib.load(fh)
68+
assert pin["git"] == HARBOR_DEFAULT_SOURCE
69+
assert pin["rev"] == HARBOR_DEFAULT_SHA
70+
71+
6572
def test_probe_harbor_ref_matches_pyproject_pin():
6673
# The probe fetches harbor at ``harbor_source_ref``; it must resolve to the
6774
# exact code the worker image runs, or probe trials inspect different harbor

0 commit comments

Comments
 (0)