Skip to content
Merged
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
56 changes: 40 additions & 16 deletions scripts/ci/agent_mention_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from __future__ import annotations

import argparse
import concurrent.futures
import hashlib
import json
import os
Expand Down Expand Up @@ -463,24 +464,47 @@ def dispatched_agents(
artifact_cache = (
ledger_artifact_cache if ledger_artifact_cache is not None else {}
)

def _fetch_agent(agent: str) -> None:
"""Fetch and cache the exact-name artifact lookup for one agent."""
artifact_name = agent_ledger_artifact_name(request, agent)
response = dispatch_client.request(
[
LEDGER_ARTIFACTS_ENDPOINT,
"-X",
"GET",
"-f",
f"name={artifact_name}",
"-f",
"per_page=100",
]
)
artifact_cache[artifact_name] = bool(
_artifact_records(response, expected_name=artifact_name)
)

agents_to_fetch = [
agent
for agent in candidates
if agent_ledger_artifact_name(request, agent) not in artifact_cache
]
if len(agents_to_fetch) <= 1:
for agent in agents_to_fetch:
_fetch_agent(agent)
else:
# Bounded concurrency for an otherwise-sequential N+1 network fetch.
# list(executor.map(...)) already blocks until every submitted call
# finishes (or raises) before this function proceeds, so shutdown's
# own wait has nothing left to wait for on the success path.
executor = concurrent.futures.ThreadPoolExecutor(max_workers=5)
try:
list(executor.map(_fetch_agent, agents_to_fetch))
finally:
executor.shutdown(wait=False, cancel_futures=True)

for agent in candidates:
artifact_name = agent_ledger_artifact_name(request, agent)
if artifact_name not in artifact_cache:
response = dispatch_client.request(
[
LEDGER_ARTIFACTS_ENDPOINT,
"-X",
"GET",
"-f",
f"name={artifact_name}",
"-f",
"per_page=100",
]
)
artifact_cache[artifact_name] = bool(
_artifact_records(response, expected_name=artifact_name)
)
if artifact_cache[artifact_name]:
if artifact_cache.get(artifact_name):
observed.add(agent)
return frozenset(observed)

Expand Down
54 changes: 54 additions & 0 deletions tests/test_agent_mention_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -744,3 +744,57 @@ def test_load_event_and_main_paths(tmp_path: Path, monkeypatch, capsys) -> None:
)
assert module.main(["--event-path", str(valid_path), "--dry-run"]) == 0
assert captured[0][1]["dry_run"] is True


def test_dispatched_agents_fetches_multiple_candidates_concurrently() -> None:
"""More than one uncached agent uses the bounded thread-pool fetch path."""

module = load_module()
request = module.parse_event(
event("@cwl-noema-review @opencode-agent")
)
assert request is not None
client = FakeClient()

observed = module.dispatched_agents(request, client)

assert observed == frozenset()
artifact_calls = [
args for args, _ in client.calls if args[0].endswith("/actions/artifacts")
]
assert len(artifact_calls) == 2


def test_dispatched_agents_single_candidate_skips_thread_pool() -> None:
"""Exactly one uncached agent stays on the plain sequential path."""

module = load_module()
request = module.parse_event(event("@opencode-agent"))
assert request is not None
client = FakeClient()

observed = module.dispatched_agents(request, client)

assert observed == frozenset()
assert len(client.calls) == 1


def test_dispatched_agents_reuses_the_caller_owned_cache() -> None:
"""A pre-populated cache entry never triggers a redundant API call."""

module = load_module()
request = module.parse_event(
event("@cwl-noema-review @opencode-agent")
)
assert request is not None
client = FakeClient()
cached_name = module.agent_ledger_artifact_name(request, "cwl-noema-review")

observed = module.dispatched_agents(
request,
client,
ledger_artifact_cache={cached_name: True},
)

assert observed == frozenset({"cwl-noema-review"})
assert len(client.calls) == 1
Loading