From fb5dbb20eebd45991ea6fae314fb4d23092ccb23 Mon Sep 17 00:00:00 2001 From: Adam Moussa <166072409+amoussa1229@users.noreply.github.com> Date: Fri, 15 May 2026 13:13:41 -0400 Subject: [PATCH 1/3] Add CI workflow and ignore .DS_Store MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes the no-CI gap surfaced in the post-merge retrospective. The org reusable workflows live under Sea-Haven-Industries and assume SAM/CDK projects — orchestrator is a personal CLI tool with neither, so this is a standalone workflow on the same conventions (actions/checkout@v6, Python 3.12, ruff). - lint job: ruff check + ruff format --check. - test-collect job: installs requirements + runs `pytest --collect-only`. Catches import errors and golden-set test discovery regressions without needing live ANTHROPIC/COMPOSIO secrets — full pytest stays a local pre-push responsibility. Also adds .DS_Store to the local .gitignore (also covered by the user global gitignore, but belt-and-suspenders). --- .github/workflows/ci.yaml | 50 +++++++++++++++++++++++++++++++++++++++ .gitignore | 1 + 2 files changed, 51 insertions(+) create mode 100644 .github/workflows/ci.yaml diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..f348552 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,50 @@ +name: CI + +on: + pull_request: + branches: [main] + push: + branches: [main] + +jobs: + lint: + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@v6 + + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install ruff + run: pip install ruff + + - name: Ruff check + run: ruff check . + + - name: Ruff format check + run: ruff format --check . + + test-collect: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v6 + + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + cache: pip + cache-dependency-path: requirements.txt + + - name: Install dependencies + run: | + pip install -r requirements.txt + pip install pytest python-dotenv + + - name: Pytest collect-only + # Verifies all test modules + their imports resolve cleanly. + # Live test execution requires ANTHROPIC_API_KEY + COMPOSIO_API_KEY + # and runs locally before push, not in CI. + run: pytest --collect-only -q diff --git a/.gitignore b/.gitignore index 43a28cb..ce218a5 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ __pycache__/ .cache/ .pytest_cache/ .ruff_cache/ +.DS_Store -- 2.50.1 From 3638d6ba964d0b1346c96a8b9d58bf30c906bf69 Mon Sep 17 00:00:00 2001 From: Adam Moussa <166072409+amoussa1229@users.noreply.github.com> Date: Fri, 15 May 2026 13:14:18 -0400 Subject: [PATCH 2/3] Bump Sonnet model ID to claude-sonnet-4-6 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit claude-sonnet-4-20250514 reaches EOL 2026-06-15. Bumping ahead of the deadline while the centralized models.py constant makes it a one-line change. Smoke-tested via run.py --route-only — no deprecation warning, router behavior unchanged. --- models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models.py b/models.py index ef6cfbc..b617131 100644 --- a/models.py +++ b/models.py @@ -6,7 +6,7 @@ from langchain_google_genai import ChatGoogleGenerativeAI from langchain_openai import ChatOpenAI # Model IDs — single source of truth. Bump here when families ship new revs. -CLAUDE_SONNET = "claude-sonnet-4-20250514" +CLAUDE_SONNET = "claude-sonnet-4-6" CLAUDE_HAIKU = "claude-haiku-4-5-20251001" OPENAI_CROSS_REVIEWER = "gpt-4.1" GEMINI_SCANNER = "gemini-2.5-pro" -- 2.50.1 From 2bb8365972c4bf975e09ab9443d1c50b94a62e57 Mon Sep 17 00:00:00 2001 From: Adam Moussa <166072409+amoussa1229@users.noreply.github.com> Date: Fri, 15 May 2026 13:18:04 -0400 Subject: [PATCH 3/3] Fix golden-set collection when provider keys are absent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI's `pytest --collect-only` was exiting 5 (no tests collected) because the test module used `pytest.skip(..., allow_module_level=True)` at import time — skipped modules never enter the collection phase. Switch to a per-test `skipif` marker driven by ANTHROPIC_API_KEY + COMPOSIO_API_KEY presence, and lazy-import `graph.router_node` inside the test body so module import works without COMPOSIO_API_KEY. Result: 21 tests collect in both environments; live tests run only when both keys are present. Verified locally with env -i. --- tests/test_routing_golden.py | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/tests/test_routing_golden.py b/tests/test_routing_golden.py index 3e318b8..8584201 100644 --- a/tests/test_routing_golden.py +++ b/tests/test_routing_golden.py @@ -1,8 +1,9 @@ """Golden-set routing test for the structured router. -20 labelled tasks → expected agent. Skipped if provider keys are missing -(ANTHROPIC for the router LLM, COMPOSIO because importing the graph eagerly -loads tools). Run with: +20 labelled tasks → expected agent. Live tests require ANTHROPIC_API_KEY + +COMPOSIO_API_KEY; without them the parametrized cases skip at runtime but +the module still collects (so `pytest --collect-only` in CI works without +secrets). Run with: pytest tests/test_routing_golden.py -v """ @@ -14,17 +15,12 @@ from dotenv import load_dotenv load_dotenv(os.path.join(os.path.dirname(__file__), "..", ".env")) -if not os.getenv("ANTHROPIC_API_KEY"): - pytest.skip( - "ANTHROPIC_API_KEY not set; skipping live router tests.", - allow_module_level=True, - ) -if not os.getenv("COMPOSIO_API_KEY"): - pytest.skip( - "COMPOSIO_API_KEY not set; skipping live router tests.", allow_module_level=True - ) - -from graph import router_node # noqa: E402 +_REQUIRED_KEYS = ("ANTHROPIC_API_KEY", "COMPOSIO_API_KEY") +_missing = [k for k in _REQUIRED_KEYS if not os.getenv(k)] +requires_keys = pytest.mark.skipif( + bool(_missing), + reason=f"Requires {', '.join(_REQUIRED_KEYS)}; missing: {', '.join(_missing)}", +) GOLDEN_SET: list[tuple[str, str]] = [ @@ -107,8 +103,13 @@ def test_golden_set_size(): assert len(GOLDEN_SET) == 20 +@requires_keys @pytest.mark.parametrize("task,expected", GOLDEN_SET) def test_router_picks_expected_agent(task: str, expected: str): + # Lazy import — graph.py loads composio_tools at module import and needs + # COMPOSIO_API_KEY. Keep the import inside the skipif-guarded test body. + from graph import router_node + out = router_node({"task": task, "messages": []}) assert out["route"] == expected, ( f"task={task!r} got={out['route']} expected={expected}" -- 2.50.1