route66 0.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,58 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.pyo
5
+ *.pyd
6
+ *.pyc
7
+ *.pdb
8
+
9
+ # Virtual environment (uv)
10
+ .venv/
11
+ venv/
12
+ env/
13
+
14
+ # Distribution / packaging
15
+ dist/
16
+ build/
17
+ *.egg-info/
18
+ *.egg
19
+ MANIFEST
20
+
21
+ # Testing
22
+ .pytest_cache/
23
+ .coverage
24
+ coverage.xml
25
+ htmlcov/
26
+ .tox/
27
+
28
+ # Linting (ruff)
29
+ .ruff_cache/
30
+
31
+ # Type checking
32
+ .mypy_cache/
33
+ .pytype/
34
+
35
+ # Jupyter
36
+ .ipynb_checkpoints/
37
+ *.ipynb
38
+
39
+ # Environment variables
40
+ .env
41
+ .env.*
42
+ !.env.example
43
+
44
+ # OS
45
+ .DS_Store
46
+ Thumbs.db
47
+ desktop.ini
48
+
49
+ # Editors
50
+ .vscode/
51
+ .idea/
52
+ *.swp
53
+ *.swo
54
+ *~
55
+
56
+ # Logs
57
+ *.log
58
+ logs/
route66-0.1.1/PKG-INFO ADDED
@@ -0,0 +1,104 @@
1
+ Metadata-Version: 2.4
2
+ Name: route66
3
+ Version: 0.1.1
4
+ Summary: route66 Python SDK
5
+ Author: route66 maintainers
6
+ License: Proprietary
7
+ Keywords: route66,sdk
8
+ Classifier: Operating System :: OS Independent
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3 :: Only
11
+ Classifier: Typing :: Typed
12
+ Requires-Python: >=3.10
13
+ Provides-Extra: dev
14
+ Requires-Dist: build>=1.2; extra == 'dev'
15
+ Requires-Dist: pytest>=8.0; extra == 'dev'
16
+ Requires-Dist: ruff>=0.5; extra == 'dev'
17
+ Requires-Dist: twine>=5.1; extra == 'dev'
18
+ Provides-Extra: semantic
19
+ Requires-Dist: sentence-transformers>=3.0; extra == 'semantic'
20
+ Description-Content-Type: text/markdown
21
+
22
+ # route66
23
+
24
+ **Intelligent Tool Router for LLM Agents** — a catalog-in library that,
25
+ given a query and a large tool catalog, returns only the relevant tools,
26
+ deduplicated, in an ordered execution plan, with policy-aware defaults.
27
+
28
+ Built against the **`router-testkit`** hackathon grading contract
29
+ (single-tool routing, ordered multi-tool plans, near-duplicate
30
+ resolution, capability-based fallback widening, deprecation policy,
31
+ out-of-scope refusal, and clarify-on-vague-destructive).
32
+
33
+ ## Layout
34
+
35
+ ```
36
+ route66/
37
+ ├── tool_router/ # The POC library (facade + retrievers + dedup)
38
+ ├── testkit_adapter.py # Bridges tool_router to the harness contract
39
+ ├── demo.py # Toy 60-tool catalog demo of the POC
40
+ ├── metrics.py # Recall/precision/token-savings eval on the toy set
41
+ ├── router-testkit/ # Hackathon input kit — DO NOT MODIFY
42
+ │ ├── catalog/ # 64 mock tools · 13 clusters · dup/version metadata
43
+ │ ├── test_cases/ # 29 scored cases across 11 categories
44
+ │ ├── registry_guardrail/# Intake validator fixtures
45
+ │ └── harness/ # run_benchmark.py (scorer) + baseline_router.py
46
+ ├── bench/ # Additional benchmarks (baseline, mutations, guardrail)
47
+ ├── specs/ # Spec-driven-development plan (SPEC-001..011)
48
+ ├── docs/ # PRDs and the hackathon problem statement
49
+ └── tests/ # Unit tests (populated as specs are executed)
50
+ ```
51
+
52
+ ## Baseline
53
+
54
+ | Router | Cases passed | Token savings |
55
+ |---------------------------------------|--------------|----------------|
56
+ | Testkit reference `baseline_router` | 7 / 29 (24%) | 94% |
57
+ | `tool_router` (this repo, current) | 5 / 29 (17%) | 90% |
58
+
59
+ The gap is deliberate and mapped in `specs/`. See
60
+ [`specs/README.md`](./specs/README.md) for the priority-ordered plan
61
+ (P0 → P1 → P2) with acceptance criteria pinned to specific `TC-*`
62
+ cases. Target after P0: ≥ 20/29. After P1: ≥ 24/29.
63
+
64
+ ## Quick start
65
+
66
+ ```bash
67
+ # Install (uv-managed)
68
+ uv sync
69
+
70
+ # Score the reference baseline
71
+ cd router-testkit/harness
72
+ python run_benchmark.py --verbose
73
+
74
+ # Score this project's router
75
+ python run_benchmark.py --router testkit_adapter:MyRouter --verbose
76
+ ```
77
+
78
+ The harness has no third-party dependencies; standard-library only.
79
+ The router itself can run on `HashingEmbedder` (zero deps) or opt-in
80
+ `SentenceTransformerEmbedder` via `uv sync --extra semantic`.
81
+
82
+ ## Where to read next
83
+
84
+ 1. **`specs/README.md`** — the executable plan. Every spec is pickable
85
+ independently and has acceptance criteria tied to testkit case IDs.
86
+ 2. **`router-testkit/README.md`** — the grading contract (authoritative).
87
+ 3. **`tool_router/README.md`** (below `tool_router/`) — the current POC
88
+ library docs. Will be reframed in SPEC-011.
89
+ 4. **`docs/hackathon-definition.md`** — the problem statement.
90
+ 5. **`docs/PRD.md`** — the initial PRD.
91
+
92
+ ## Toolchain
93
+
94
+ - Python ≥ 3.14 (pinned in `.python-version`, managed by uv)
95
+ - Ruff for lint + format
96
+ - Pytest for unit tests (see `pyproject.toml`'s `[dev]` extra)
97
+
98
+ ## Dev
99
+
100
+ ```bash
101
+ uv run ruff check .
102
+ uv run ruff format .
103
+ uv run pytest
104
+ ```
@@ -0,0 +1,83 @@
1
+ # route66
2
+
3
+ **Intelligent Tool Router for LLM Agents** — a catalog-in library that,
4
+ given a query and a large tool catalog, returns only the relevant tools,
5
+ deduplicated, in an ordered execution plan, with policy-aware defaults.
6
+
7
+ Built against the **`router-testkit`** hackathon grading contract
8
+ (single-tool routing, ordered multi-tool plans, near-duplicate
9
+ resolution, capability-based fallback widening, deprecation policy,
10
+ out-of-scope refusal, and clarify-on-vague-destructive).
11
+
12
+ ## Layout
13
+
14
+ ```
15
+ route66/
16
+ ├── tool_router/ # The POC library (facade + retrievers + dedup)
17
+ ├── testkit_adapter.py # Bridges tool_router to the harness contract
18
+ ├── demo.py # Toy 60-tool catalog demo of the POC
19
+ ├── metrics.py # Recall/precision/token-savings eval on the toy set
20
+ ├── router-testkit/ # Hackathon input kit — DO NOT MODIFY
21
+ │ ├── catalog/ # 64 mock tools · 13 clusters · dup/version metadata
22
+ │ ├── test_cases/ # 29 scored cases across 11 categories
23
+ │ ├── registry_guardrail/# Intake validator fixtures
24
+ │ └── harness/ # run_benchmark.py (scorer) + baseline_router.py
25
+ ├── bench/ # Additional benchmarks (baseline, mutations, guardrail)
26
+ ├── specs/ # Spec-driven-development plan (SPEC-001..011)
27
+ ├── docs/ # PRDs and the hackathon problem statement
28
+ └── tests/ # Unit tests (populated as specs are executed)
29
+ ```
30
+
31
+ ## Baseline
32
+
33
+ | Router | Cases passed | Token savings |
34
+ |---------------------------------------|--------------|----------------|
35
+ | Testkit reference `baseline_router` | 7 / 29 (24%) | 94% |
36
+ | `tool_router` (this repo, current) | 5 / 29 (17%) | 90% |
37
+
38
+ The gap is deliberate and mapped in `specs/`. See
39
+ [`specs/README.md`](./specs/README.md) for the priority-ordered plan
40
+ (P0 → P1 → P2) with acceptance criteria pinned to specific `TC-*`
41
+ cases. Target after P0: ≥ 20/29. After P1: ≥ 24/29.
42
+
43
+ ## Quick start
44
+
45
+ ```bash
46
+ # Install (uv-managed)
47
+ uv sync
48
+
49
+ # Score the reference baseline
50
+ cd router-testkit/harness
51
+ python run_benchmark.py --verbose
52
+
53
+ # Score this project's router
54
+ python run_benchmark.py --router testkit_adapter:MyRouter --verbose
55
+ ```
56
+
57
+ The harness has no third-party dependencies; standard-library only.
58
+ The router itself can run on `HashingEmbedder` (zero deps) or opt-in
59
+ `SentenceTransformerEmbedder` via `uv sync --extra semantic`.
60
+
61
+ ## Where to read next
62
+
63
+ 1. **`specs/README.md`** — the executable plan. Every spec is pickable
64
+ independently and has acceptance criteria tied to testkit case IDs.
65
+ 2. **`router-testkit/README.md`** — the grading contract (authoritative).
66
+ 3. **`tool_router/README.md`** (below `tool_router/`) — the current POC
67
+ library docs. Will be reframed in SPEC-011.
68
+ 4. **`docs/hackathon-definition.md`** — the problem statement.
69
+ 5. **`docs/PRD.md`** — the initial PRD.
70
+
71
+ ## Toolchain
72
+
73
+ - Python ≥ 3.14 (pinned in `.python-version`, managed by uv)
74
+ - Ruff for lint + format
75
+ - Pytest for unit tests (see `pyproject.toml`'s `[dev]` extra)
76
+
77
+ ## Dev
78
+
79
+ ```bash
80
+ uv run ruff check .
81
+ uv run ruff format .
82
+ uv run pytest
83
+ ```
@@ -0,0 +1,115 @@
1
+ # bench/ — Conduit Evaluation Suite
2
+
3
+ This directory holds the benchmark that makes Conduit's claims falsifiable. Nothing in the PRD is defensible without the numbers this suite produces.
4
+
5
+ ## Files
6
+
7
+ | File | Purpose |
8
+ |---|---|
9
+ | `tools.jsonl` | The 500-tool catalog. One tool per line. Ground truth for what the router picks *from*. |
10
+ | `queries.jsonl` | The 200-query labeled eval set. Each query has a `gold_tools` list — the ground-truth tool(s) that should be selected. |
11
+ | `queries_ood.jsonl` | 20-query out-of-distribution slice — sampled/authored from real oncall vernacular, held out from all tuning. This is the honest test. |
12
+ | `run_baseline.py` | Long-context baseline runner (Sonnet 4.5 + prompt caching + all 500 tools in context). Produces `baseline_longcontext.md`. See §2.5 of PRD. |
13
+ | `run_conduit.py` | Conduit routed pipeline runner. Produces `report.md`. |
14
+ | `metrics.py` | Shared metric computations (precision@k, recall@k, token counting, cost). |
15
+ | `baseline_longcontext.md` | Auto-generated report from `run_baseline.py`. **This file drives the §2.5 gate decision.** |
16
+ | `report.md` | Auto-generated final report comparing all baselines. |
17
+
18
+ ## Schemas
19
+
20
+ ### `tools.jsonl` — one tool per line
21
+
22
+ ```json
23
+ {
24
+ "id": "datadog.query_service_map",
25
+ "server": "datadog",
26
+ "name": "query_service_map",
27
+ "description": "Query the Datadog service map to identify upstream/downstream dependencies of a service. Useful for diagnosing latency propagation and identifying the source of a cascading failure.",
28
+ "params_summary": "service_name (str), time_window_minutes (int, default 15), env (str, optional)",
29
+ "returns_summary": "list of connected services with edge metadata (latency_p95, error_rate)",
30
+ "tags": ["datadog", "observability", "dependency-graph", "read"],
31
+ "policy_tags": ["read-only"],
32
+ "signature": "query_service_map(service_name: str, time_window_minutes: int = 15, env: str | None = None) -> ServiceMap"
33
+ }
34
+ ```
35
+
36
+ - `id` — globally-unique, dotted namespace `{server}.{name}`.
37
+ - `signature` — the compressed form injected into prompts (see PRD §3.6).
38
+ - `policy_tags` — used by §3.7 policy filter (P1).
39
+ - `tags` — semantic tags; used for BM25 keyword match.
40
+
41
+ ### `queries.jsonl` — one labeled query per line
42
+
43
+ ```json
44
+ {
45
+ "id": "q_001",
46
+ "archetype": "bad_deploy",
47
+ "difficulty": "easy",
48
+ "query": "Our checkout service p99 latency spiked after the 2:14pm deploy — can you find which commit and roll it back?",
49
+ "gold_tools": ["datadog.query_latency", "github.get_recent_deploys", "argocd.rollback"],
50
+ "gold_k": 3,
51
+ "notes": "Multi-step SRE flow. Router should return the diagnostic tool plus the deploy-history tool plus the mitigation tool."
52
+ }
53
+ ```
54
+
55
+ - `archetype` — one of 10 SRE incident archetypes (see below).
56
+ - `difficulty` — `easy | medium | hard`. Hard queries use colloquial language, abbreviations, or ambiguous phrasing.
57
+ - `gold_tools` — ordered list of tools that constitute the correct chain. Precision@k evaluates whether these appear in the top-k selection.
58
+ - `gold_k` — expected number of tools for this query. Used to evaluate the dynamic-K gate (§3.5).
59
+
60
+ ### `queries_ood.jsonl` — same schema as `queries.jsonl`
61
+
62
+ Sampled from real oncall vernacular. Held out from all tuning. If Conduit hits precision@3 ≥ 80% here, the "94/8" claim is real. If it doesn't, we don't ship the number.
63
+
64
+ ## Archetypes (10 SRE incident categories)
65
+
66
+ | Archetype | Description |
67
+ |---|---|
68
+ | `bad_deploy` | Regression introduced by a recent deploy |
69
+ | `memory_leak` | Slow-growing memory that eventually OOMs |
70
+ | `dns_failure` | DNS resolution issues (misconfig, expired records, propagation) |
71
+ | `cert_expiry` | TLS certificate expired or about to expire |
72
+ | `db_failover` | Database primary crashed or is being failed over |
73
+ | `cache_stampede` | Thundering-herd behavior after cache invalidation |
74
+ | `quota_exhaustion` | API rate limit or cloud quota exceeded |
75
+ | `secret_rotation` | Credentials rotated, breaking downstream services |
76
+ | `capacity_spike` | Sudden traffic surge exceeding capacity |
77
+ | `dependency_outage` | A third-party or upstream dependency is down |
78
+
79
+ Each archetype targets 20 queries in `queries.jsonl` — 6 easy, 8 medium, 6 hard.
80
+
81
+ ## Metrics
82
+
83
+ - **precision@k** — fraction of queries where any gold tool appears in the top-k selection.
84
+ - **recall@k** — fraction of gold tools present in the top-k selection, averaged over queries.
85
+ - **exact_match@k** — fraction of queries where *all* gold tools appear in the top-k.
86
+ - **prompt_tokens** — median + p95 tokens sent to the LLM per query (before prompt-cache credit).
87
+ - **effective_tokens** — median + p95 tokens billed after prompt caching.
88
+ - **cost_per_query** — USD, using published Sonnet 4.5 pricing.
89
+ - **latency_p50 / latency_p95** — end-to-end latency including LLM response.
90
+ - **hallucination_rate** — fraction of tool calls where the LLM invented a tool name not in the catalog.
91
+ - **end_to_end_success** — human-graded: did the response answer the user's question correctly?
92
+
93
+ ## Running
94
+
95
+ ```bash
96
+ # 1. Long-context baseline (the §2.5 gate)
97
+ python bench/run_baseline.py --tools bench/tools.jsonl --queries bench/queries.jsonl --out bench/baseline_longcontext.md
98
+
99
+ # 2. Conduit routed pipeline (once §2.5 gate says "build")
100
+ python bench/run_conduit.py --tools bench/tools.jsonl --queries bench/queries.jsonl --out bench/report.md
101
+
102
+ # 3. Out-of-distribution slice (both pipelines)
103
+ python bench/run_baseline.py --queries bench/queries_ood.jsonl --out bench/baseline_ood.md
104
+ python bench/run_conduit.py --queries bench/queries_ood.jsonl --out bench/report_ood.md
105
+ ```
106
+
107
+ ## The §2.5 gate
108
+
109
+ After `run_baseline.py` completes, read `baseline_longcontext.md`. Follow the decision table in PRD §2.5:
110
+
111
+ - Baseline precision@3 ≥ 88% → **strip retrieval, ship the long-context path.**
112
+ - Baseline precision@3 80–88% → build retrieval, demote §3.7/§3.8 to P1.
113
+ - Baseline precision@3 < 80% → build retrieval, mandatory validation gates.
114
+
115
+ Do this by **Day 2 EOD**. Everything downstream depends on it.
@@ -0,0 +1,66 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.25"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "route66"
7
+ dynamic = ["version"]
8
+ description = "route66 Python SDK"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = { text = "Proprietary" }
12
+ authors = [{ name = "route66 maintainers" }]
13
+ keywords = ["route66", "sdk"]
14
+ classifiers = [
15
+ "Programming Language :: Python :: 3",
16
+ "Programming Language :: Python :: 3 :: Only",
17
+ "Operating System :: OS Independent",
18
+ "Typing :: Typed",
19
+ ]
20
+ dependencies = []
21
+
22
+ [project.optional-dependencies]
23
+ semantic = ["sentence-transformers>=3.0"]
24
+ dev = [
25
+ "ruff>=0.5",
26
+ "pytest>=8.0",
27
+ "build>=1.2",
28
+ "twine>=5.1",
29
+ "pytest>=8.0",
30
+ ]
31
+
32
+ [project.scripts]
33
+ route66 = "route66.__main__:main"
34
+
35
+ # ---------------------------------------------------------------------------
36
+ # Build backend (hatchling) — src layout
37
+ # ---------------------------------------------------------------------------
38
+ [tool.hatch.version]
39
+ # The version is stamped into src/route66/_version.py by the release pipeline
40
+ # just before build. Keeping it in a dedicated file (instead of pyproject.toml)
41
+ # lets each provider release its own version without touching this file.
42
+ source = "regex"
43
+ path = "src/route66/_version.py"
44
+ pattern = "^__version__ = \"(?P<version>[^\"]+)\""
45
+
46
+ [tool.hatch.build.targets.wheel]
47
+ packages = ["src/route66"]
48
+
49
+ [tool.hatch.build.targets.sdist]
50
+ include = [
51
+ "src/route66",
52
+ "README.md",
53
+ "pyproject.toml",
54
+ "release/",
55
+ ]
56
+
57
+ # ---------------------------------------------------------------------------
58
+ # Tooling
59
+ # ---------------------------------------------------------------------------
60
+ [tool.ruff]
61
+ line-length = 100
62
+ target-version = "py310"
63
+
64
+ [tool.pytest.ini_options]
65
+ testpaths = ["tests"]
66
+ addopts = "-ra"
@@ -0,0 +1,23 @@
1
+ # route66 — release changelog
2
+
3
+ Each provider tracks its own version line. Entries are appended by the release
4
+ pipeline (`ci/scripts/record_release.py`) so the file below is the source of
5
+ truth for what shipped where.
6
+
7
+ ## generic
8
+
9
+ <!-- generic:begin -->
10
+ - _no releases yet_
11
+ <!-- generic:end -->
12
+
13
+ ## gitlab (GitLab Package Registry)
14
+
15
+ <!-- gitlab:begin -->
16
+ - _no releases yet_
17
+ <!-- gitlab:end -->
18
+
19
+ ## pypi (public PyPI)
20
+
21
+ <!-- pypi:begin -->
22
+ - _no releases yet_
23
+ <!-- pypi:end -->
@@ -0,0 +1,152 @@
1
+ # route66 — release infrastructure
2
+
3
+ route66 ships as a Python package to two provider registries, each with its
4
+ own independently-managed version line, plus a `generic` line that fans out to
5
+ both.
6
+
7
+ ## Providers
8
+
9
+ | Provider | Registry | Version file | Auth in CI |
10
+ | --------- | -------------------------------------------- | ----------------------------------------- | --------------------------- |
11
+ | `gitlab` | Private GitLab project Package Registry | `release/versions/gitlab.json` | `CI_JOB_TOKEN` (built-in) |
12
+ | `pypi` | Public PyPI (`pypi.org`) | `release/versions/pypi.json` | `PYPI_API_TOKEN` (variable) |
13
+ | `generic` | Fans out to **both** providers with a shared | `release/versions/generic.json` (+ writes | uses provider tokens above |
14
+ | | version number | into each provider file as well) | |
15
+
16
+ Each provider's file keeps a `current` version and an append-only `history`
17
+ list. The release pipeline never rewrites a past entry — it only updates the
18
+ one for the version that was just published (idempotent re-runs) or prepends a
19
+ new entry.
20
+
21
+ ## Layout
22
+
23
+ ```
24
+ route66/
25
+ ├── src/route66/ # SDK source (src-layout)
26
+ │ ├── __init__.py
27
+ │ ├── __main__.py
28
+ │ └── _version.py # stamped by ci/scripts/bump_version.py
29
+ ├── tests/
30
+ ├── ci/
31
+ │ ├── release.gitlab-ci.yml # included by .gitlab-ci.yml
32
+ │ └── scripts/
33
+ │ ├── _common.py
34
+ │ ├── bump_version.py
35
+ │ ├── publish.py
36
+ │ └── record_release.py
37
+ ├── release/
38
+ │ ├── CHANGELOG.md # per-provider sections, auto-updated
39
+ │ └── versions/
40
+ │ ├── generic.json
41
+ │ ├── gitlab.json
42
+ │ └── pypi.json
43
+ ├── .gitlab-ci.yml
44
+ └── pyproject.toml
45
+ ```
46
+
47
+ ## Continuous pipeline
48
+
49
+ Runs on every push / MR:
50
+
51
+ 1. **lint** — `ruff check`.
52
+ 2. **test** — `pytest`.
53
+ 3. **build** — `python -m build` → uploads `dist/` as a job artifact.
54
+
55
+ ## On-demand release pipelines
56
+
57
+ All three release jobs are `when: manual` and only appear on the default
58
+ (protected) branch. Trigger from **CI/CD → Pipelines → Run pipeline**, choose
59
+ the job, and (optionally) set variables:
60
+
61
+ | Variable | Default | Meaning |
62
+ | --------------- | ------- | ---------------------------------------------------- |
63
+ | `BUMP` | `patch` | One of `patch` / `minor` / `major`. |
64
+ | `VERSION` | *empty* | Explicit `x.y.z` — overrides `BUMP` when set. |
65
+ | `RELEASE_NOTES` | *empty* | Free-form text appended to the changelog entry. |
66
+
67
+ ### `release:gitlab`
68
+
69
+ Publishes to the private GitLab Package Registry using `CI_JOB_TOKEN`. No
70
+ extra project variable required. Endpoint is derived from `CI_API_V4_URL` and
71
+ `CI_PROJECT_ID` at runtime, so the same job works on any GitLab (SaaS or
72
+ self-hosted).
73
+
74
+ Install a released version:
75
+
76
+ ```bash
77
+ pip install route66 \
78
+ --index-url https://__token__:<deploy-token>@<gitlab-host>/api/v4/projects/<project-id>/packages/pypi/simple
79
+ ```
80
+
81
+ ### `release:pypi`
82
+
83
+ Publishes to public PyPI. Requires a **project-scoped** API token added as a
84
+ masked + protected CI variable named `PYPI_API_TOKEN`.
85
+
86
+ Install:
87
+
88
+ ```bash
89
+ pip install route66
90
+ ```
91
+
92
+ ### `release:generic`
93
+
94
+ Bumps the shared version in `release/versions/generic.json`, then fans out and
95
+ publishes to `gitlab` **and** `pypi` with the same version number. Each
96
+ provider's own history file gets an entry for that version too, so any
97
+ provider can be inspected independently.
98
+
99
+ ## Version history model
100
+
101
+ Every publish produces exactly one entry in the affected provider file(s):
102
+
103
+ ```jsonc
104
+ {
105
+ "version": "1.4.2",
106
+ "released_at": "2026-07-14T12:34:56+00:00",
107
+ "commit": "<full sha>",
108
+ "pipeline": "<pipeline url>",
109
+ "artifacts": ["route66-1.4.2-py3-none-any.whl", "route66-1.4.2.tar.gz"],
110
+ "notes": "..."
111
+ }
112
+ ```
113
+
114
+ Because provider version files are independent, the `pypi` line can lag or
115
+ lead the `gitlab` line freely (e.g. hotfix a private release without touching
116
+ public PyPI). The `generic` line acts as a "canonical" release marker that
117
+ guarantees both providers agree.
118
+
119
+ ## Required CI/CD variables
120
+
121
+ Set these under **Settings → CI/CD → Variables** on the route66 project:
122
+
123
+ | Name | Scope | Notes |
124
+ | ------------------- | -------------------- | ------------------------------------------------- |
125
+ | `GITLAB_PUSH_TOKEN` | Protected + Masked | Project access token with `write_repository`. Used to push the version-bump commit + tag back to the default branch. |
126
+ | `PYPI_API_TOKEN` | Protected + Masked | Only needed for `release:pypi` and `release:generic`. |
127
+
128
+ `CI_JOB_TOKEN` is provided automatically by GitLab and is already scoped to
129
+ the project's Package Registry.
130
+
131
+ ## Tagging convention
132
+
133
+ Each release creates an annotated tag of the form `{provider}/v{X.Y.Z}`, e.g.
134
+ `gitlab/v1.4.2`, `pypi/v1.4.2`, `generic/v2.0.0`. That way `git tag --list`
135
+ tells you every artifact that ever shipped for a given provider, and provider
136
+ lines never collide.
137
+
138
+ ## Local dry-run
139
+
140
+ To verify a bump without publishing:
141
+
142
+ ```bash
143
+ python ci/scripts/bump_version.py --provider gitlab --kind patch
144
+ cat release/versions/gitlab.json
145
+ python -m build
146
+ ```
147
+
148
+ Then discard the local changes:
149
+
150
+ ```bash
151
+ git checkout -- src/route66/_version.py release/versions/gitlab.json
152
+ ```
@@ -0,0 +1,15 @@
1
+ {
2
+ "provider": "generic",
3
+ "description": "Shared/coordinated release version. Bumped by the generic release pipeline and published to every provider.",
4
+ "current": "0.1.0",
5
+ "history": [
6
+ {
7
+ "version": "0.1.0",
8
+ "released_at": null,
9
+ "commit": null,
10
+ "pipeline": null,
11
+ "artifacts": [],
12
+ "notes": "Initial scaffold. No published artifact yet."
13
+ }
14
+ ]
15
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "provider": "gitlab",
3
+ "description": "GitLab Package Registry (private, project-scoped PyPI index).",
4
+ "index_url_template": "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/pypi",
5
+ "current": "0.1.0",
6
+ "history": [
7
+ {
8
+ "version": "0.1.0",
9
+ "released_at": null,
10
+ "commit": null,
11
+ "pipeline": null,
12
+ "artifacts": [],
13
+ "notes": "Initial scaffold. No published artifact yet."
14
+ }
15
+ ]
16
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "provider": "pypi",
3
+ "description": "Public PyPI (pypi.org).",
4
+ "index_url": "https://upload.pypi.org/legacy/",
5
+ "current": "0.1.0",
6
+ "history": [
7
+ {
8
+ "version": "0.1.0",
9
+ "released_at": null,
10
+ "commit": null,
11
+ "pipeline": null,
12
+ "artifacts": [],
13
+ "notes": "Initial scaffold. No published artifact yet."
14
+ }
15
+ ]
16
+ }
@@ -0,0 +1,137 @@
1
+ # AI Tool-Routing Hackathon — Input Test Kit
2
+
3
+ A ready-to-run input set for building and grading an **intelligent tool-routing
4
+ layer** that filters a large enterprise tool catalog down to a small,
5
+ high-precision subset per query.
6
+
7
+ Everything here is *input* for participants: a mock catalog, a scored test
8
+ suite, registry-guardrail fixtures, catalog-churn scenarios, and a benchmark
9
+ harness with a reference baseline router to beat.
10
+
11
+ ```
12
+ router-testkit/
13
+ ├── catalog/
14
+ │ └── tools_catalog.json 64 mock tools · 13 clusters · dup/version metadata
15
+ ├── registry_guardrail/
16
+ │ └── submissions.json valid + malformed intake fixtures (accept/reject)
17
+ ├── test_cases/
18
+ │ ├── 01_single_tool.json ── 29 scored query packets across 11 categories
19
+ │ ├── 02_multi_tool_ordered.json
20
+ │ ├── 03_deduplication.json
21
+ │ ├── 04_repeat_count.json
22
+ │ ├── 05_clarification.json
23
+ │ ├── 06_fallback_widening.json
24
+ │ ├── 07_backward_compat.json
25
+ │ ├── 08_negative_out_of_scope.json
26
+ │ ├── 09_cross_cluster.json
27
+ │ ├── 10_positional_bias.json
28
+ │ ├── 11_scope_overlap.json
29
+ │ ├── catalog_mutations.json add / remove / replace / upgrade resilience
30
+ │ └── _index.json
31
+ └── harness/
32
+ ├── run_benchmark.py scorer (prints metrics; no dependencies)
33
+ └── baseline_router.py naive reference router (REPLACE THIS)
34
+ ```
35
+
36
+ ## Quick start
37
+
38
+ Standard-library Python only — nothing to install.
39
+
40
+ ```bash
41
+ cd harness
42
+ python run_benchmark.py # runs the baseline (~24% accuracy)
43
+ python run_benchmark.py --router mymod:MyRouter # run your own
44
+ python run_benchmark.py --verbose # also print why each case failed
45
+ ```
46
+
47
+ Your router is any class with:
48
+
49
+ ```python
50
+ class MyRouter:
51
+ def __init__(self, catalog): ...
52
+ def route(self, query, context=None) -> dict:
53
+ return {
54
+ "selected_tools": ["fin.get_revenue_report", ...], # the K schemas you'd inject
55
+ "plan": ["fin.get_revenue_report", ...], # ordered call plan (ids may repeat)
56
+ "clarify": False, # True => you asked instead of acting
57
+ "clarify_question": None,
58
+ }
59
+ ```
60
+
61
+ ## The catalog (`catalog/tools_catalog.json`)
62
+
63
+ 64 tools over 13 clusters (finance, communication, analytics, hr, identity,
64
+ it_devops, crm, calendar, documents, cloud_storage, marketing, legal,
65
+ data_export). It carries the metadata your router is expected to exploit:
66
+
67
+ - `near_duplicate_groups` — deliberately overlapping tools. E.g. the classic
68
+ `get_user` / `fetch_profile` / `lookup_member` / `get_user_by_email` quartet,
69
+ two revenue fetchers, two chart makers, `create_ticket` vs `open_incident`.
70
+ - `version_families` — `create_invoice` (deprecated v1) coexists with
71
+ `create_invoice_v2` (current). Each tool has `deprecated`, `replaces`,
72
+ `replaced_by`, `sunset_date`.
73
+ - `cross_cluster_traps` — tools whose **name** points at the wrong cluster:
74
+ `email_report` lives in data_export (not communication); `send_campaign` is
75
+ a marketing blast, not a 1:1 email.
76
+ - Per-tool `side_effects` (`read` / `write` / `destructive`) so you can be
77
+ conservative about routing destructive actions on vague queries.
78
+
79
+ ## The scored suite (`test_cases/*.json`)
80
+
81
+ Each case has a machine-checkable `expected` block:
82
+
83
+ | field | meaning |
84
+ |---|---|
85
+ | `tools_required` | `{tool_id, min_calls, max_calls}` — must be selected and called within the count band |
86
+ | `order` | ordered list that must appear as a **subsequence** of your `plan` (or `null`) |
87
+ | `forbidden` | tool ids that must NOT be selected. `"*"` means *no tool at all* |
88
+ | `should_clarify` | router should ask, not act |
89
+ | `allow_any_of` | dedup groups: pick **exactly one** id per group |
90
+
91
+ Categories map 1:1 to the problem's pillars:
92
+
93
+ - **single_tool / cross_cluster** — basic and multi-cluster routing accuracy.
94
+ - **multi_tool_ordered** — the canonical *"pull revenue → make chart → email to
95
+ finance"* chain, with ordering enforced.
96
+ - **deduplication** — the near-dup groups; injecting all three user lookups
97
+ fails, injecting one passes.
98
+ - **repeat_count** — same tool invoked N times (e.g. restart 3 services →
99
+ `restart_service` ×3).
100
+ - **clarification / negative_out_of_scope** — must *not* fabricate a tool call.
101
+ - **fallback_widening** — the initially-obvious tool lacks a needed field/scope
102
+ (e.g. `send_email` can't attach; identity lookup has no SSN); the case encodes
103
+ the correct widen target.
104
+ - **backward_compat** — unversioned request → current tool; explicit "legacy v1"
105
+ → deprecated tool; genuinely ambiguous → clarify.
106
+ - **positional_bias / scope_overlap** — set `context.shuffle_catalog=true` and
107
+ present near-dups in random order; correct pick must be signal-driven, not
108
+ position-driven (DMS vs raw S3, etc.).
109
+
110
+ ## Registry guardrail (`registry_guardrail/submissions.json`)
111
+
112
+ Feed each `invalid_submissions` entry to your intake validator; it must reject
113
+ with the given `expected_reason` (vague/too-long description, bad id/name,
114
+ missing field, duplicate id, bad param type, too many params, bad semver,
115
+ dangling `replaced_by`, malformed JSON). The two `valid_submissions` must pass.
116
+
117
+ ## Catalog churn (`test_cases/catalog_mutations.json`)
118
+
119
+ Directly answers the "what if tools change?" evaluation questions. Apply each
120
+ `delta` to the catalog, re-run the probe, and assert the invariant:
121
+
122
+ - **add** — new tool routable immediately, no regressions elsewhere.
123
+ - **remove** — depended-on tool gone → graceful degradation, no crash.
124
+ - **replace** — renamed tool routes by capability, not hardcoded id.
125
+ - **upgrade (both alive)** — `PREFER_CURRENT` unless an explicit version signal;
126
+ never inject both versions at once.
127
+ - **scale stress** — auto-grow to ~104 tools; accuracy and token savings must hold.
128
+
129
+ ## Scoring & metrics
130
+
131
+ `run_benchmark.py` checks each case and prints, in plain text: a PASS/FAIL line
132
+ per case (with token cost), a per-category tally, and a summary with overall
133
+ accuracy and token savings (average routed tokens vs. the full-catalog dump).
134
+ Run with `--verbose` to see the reason each failed case failed. No charts, no
135
+ dependencies — just numbers you can read or pipe.
136
+
137
+ Baseline reference: ~24% case accuracy, ~94% token savings. Beat both.
@@ -0,0 +1,110 @@
1
+ # Route66 — Spec Index
2
+
3
+ Spec-driven development for the hackathon build-out of the `tool_router`
4
+ library (this repo) against the `router-testkit` grading contract.
5
+
6
+ ## How to use this directory
7
+
8
+ - Each spec is **pickable independently**. If the "Dependencies" block is
9
+ empty or its dependencies are marked ✅, you can start.
10
+ - Every spec ships with **concrete acceptance criteria tied to specific
11
+ testkit case IDs** (`TC-XYZ`). "Done" means: those cases flip PASS on the
12
+ harness *and* the unit tests defined in the spec pass.
13
+ - Read the spec top-to-bottom before writing code — the Design section
14
+ locks names, signatures, and file paths that later specs depend on.
15
+
16
+ ## Ground-truth baseline (measured 2026-07-13)
17
+
18
+ | Router | Cases | Token savings |
19
+ |---|---|---|
20
+ | Reference `baseline_router` | 7/29 (24%) | 94% |
21
+ | POC via adapter (today) | 5/29 (17%) | 90% |
22
+
23
+ Target after P0: **≥20/29 (69%)**. Target with P1: **≥24/29 (83%)**.
24
+
25
+ ## Priority tiers
26
+
27
+ - **P0 — Ship-critical.** Without this the submission is below baseline.
28
+ - **P1 — Category-unlock.** Turns a 0/N category into a passing one.
29
+ - **P2 — Pitch / polish.** Doesn't move the score much, but sells the story.
30
+
31
+ ## Specs
32
+
33
+ ### P0 — Foundation cleanup (Day 1, ~half a day total)
34
+
35
+ | ID | Title | Unblocks |
36
+ |---|---|---|
37
+ | [SPEC-001](./SPEC-001-structured-tool-metadata.md) | Structured `Tool` metadata (id, cluster, tags, deprecated, side_effects, parameters) | Every P0/P1 spec below |
38
+ | [SPEC-002](./SPEC-002-runtime-dedup.md) | Runtime near-duplicate resolution (replace index-time collapse) | Deduplication, positional_bias |
39
+ | [SPEC-003](./SPEC-003-route-context-and-plan-return.md) | `RouteContext` passthrough + return full plan (not top-1) | Everything downstream — every case scored on `plan` |
40
+
41
+ ### P0 — Core build (Days 2–4)
42
+
43
+ | ID | Title | Unblocks (testkit categories) |
44
+ |---|---|---|
45
+ | [SPEC-004](./SPEC-004-chain-planner.md) | `ChainPlanner`: multi-step ordered plans | `multi_tool_ordered`, `cross_cluster`, `fallback_widening` |
46
+ | [SPEC-005](./SPEC-005-policy-layer.md) | Policy layer: `PREFER_CURRENT`, `NO_TOOL_ON_OOS`, `CLARIFY_ON_DESTRUCTIVE_VAGUE` | `backward_compat`, `clarification`, `negative_out_of_scope` |
47
+ | [SPEC-006](./SPEC-006-capability-filter.md) | Capability-based fallback widening | `fallback_widening` |
48
+ | [SPEC-010](./SPEC-010-testkit-adapter-hardening.md) | Harden the `MyRouter` testkit adapter | Passes the actual `run_benchmark.py` scoring |
49
+
50
+ ### P1 — Category unlocks (Day 5)
51
+
52
+ | ID | Title | Unblocks |
53
+ |---|---|---|
54
+ | [SPEC-007](./SPEC-007-entity-count-extractor.md) | Entity/count extractor for repeat-call plans | `repeat_count` |
55
+ | [SPEC-008](./SPEC-008-schema-intake-validator.md) | `SchemaIntakeValidator` for registry guardrail | `registry_guardrail/submissions.json` |
56
+
57
+ ### P2 — Completeness & pitch (Days 6–7)
58
+
59
+ | ID | Title | Value |
60
+ |---|---|---|
61
+ | [SPEC-009](./SPEC-009-catalog-registry.md) | `CatalogRegistry` with add/remove/replace/upgrade deltas | `catalog_mutations.json`; the "scales to 104 tools" pitch slide |
62
+ | [SPEC-011](./SPEC-011-poc-reframing.md) | Reframe POC README + demo around what the testkit actually grades | Judge-facing story |
63
+
64
+ ## Dependency graph
65
+
66
+ ```
67
+ SPEC-001 (Tool metadata)
68
+
69
+ ├── SPEC-002 (runtime dedup)
70
+ ├── SPEC-003 (context + plan)
71
+ │ │
72
+ │ ├── SPEC-004 (planner) ──┬── SPEC-006 (capability filter)
73
+ │ ├── SPEC-005 (policy) │
74
+ │ └── SPEC-007 (entities) ──┘
75
+
76
+ ├── SPEC-008 (intake validator, independent)
77
+ └── SPEC-009 (catalog registry, depends on SPEC-001)
78
+
79
+ SPEC-010 (adapter) — depends on all P0 specs shipping
80
+ SPEC-011 (framing) — independent, do last
81
+ ```
82
+
83
+ ## Suggested pickup order (single-owner)
84
+
85
+ **Day 1:** SPEC-001 → SPEC-002 → SPEC-003 (~5 hrs, cleanup)
86
+ **Day 2:** SPEC-005 (policy layer — highest per-hour ROI)
87
+ **Day 3:** SPEC-004 (planner)
88
+ **Day 4:** SPEC-006 (capability filter) → SPEC-010 (adapter hardening)
89
+ **Day 5:** SPEC-007 → SPEC-008 (parallelizable)
90
+ **Day 6:** SPEC-009 (catalog churn)
91
+ **Day 7:** SPEC-011 (reframing) + bulletproofing
92
+
93
+ ## What is *out of scope* for these specs
94
+
95
+ Per the decision to keep-but-bypass POC assets that don't score against
96
+ this testkit, the following files are **frozen**: `session.py`,
97
+ `storage.py`, `providers.py`, `adapters.py`, and the
98
+ `AssociationRetriever` in `retrieval.py`. Do not modify them; do not
99
+ delete them. The specs below explicitly bypass them via the `RouteContext`
100
+ + adapter path.
101
+
102
+ ## Test commands (reference)
103
+
104
+ ```bash
105
+ # 1. From router-testkit/harness/:
106
+ python run_benchmark.py --router testkit_adapter:MyRouter --verbose
107
+
108
+ # 2. From :
109
+ python3 -m pytest tests/ # spec-defined unit tests
110
+ ```
@@ -0,0 +1,24 @@
1
+ """route66 - Python SDK.
2
+
3
+ Public entry point for the route66 package. Version is sourced from the
4
+ installed distribution metadata so that provider-specific builds report
5
+ the version that was actually published.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from importlib import metadata as _metadata
11
+
12
+ from ._version import __version__ as _stamped_version
13
+
14
+ try:
15
+ __version__ = _metadata.version("route66")
16
+ except _metadata.PackageNotFoundError: # pragma: no cover - local checkout
17
+ __version__ = _stamped_version
18
+
19
+ __all__ = ["__version__", "hello"]
20
+
21
+
22
+ def hello(name: str = "world") -> str:
23
+ """Return a friendly greeting. Placeholder SDK surface."""
24
+ return f"Hello from route66, {name}!"
@@ -0,0 +1,13 @@
1
+ """Allow `python -m route66`."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from . import hello
6
+
7
+
8
+ def main() -> None:
9
+ print(hello())
10
+
11
+
12
+ if __name__ == "__main__":
13
+ main()
@@ -0,0 +1,3 @@
1
+ """Version stamp for route66. Managed by ci/scripts/bump_version.py."""
2
+
3
+ __version__ = "0.1.1"
@@ -0,0 +1 @@
1
+ # Marker file so type checkers (mypy, pyright) treat route66 as a typed package.