feat(crawler): register publisher_properties agents into periodic probe loop#4848
Merged
Conversation
…be loop (#4846) Closes the circular gap where agents discovered via `authorization_type: 'publisher_properties'` in adagents.json were never included in the periodic sales-agent probe cycle. - `fanOutPublisherPropertiesAuthorizations` now calls `upsertSalesCandidate` so the declaring agent is registered in `discovered_agents` as `sales_candidate` after any adagents.json crawl path (full cycle, manager-revalidation queue, or single-domain refresh) - `crawlAllAgents` fetches `sales_candidate` rows via `getSalesCandidatesForProbe` and appends them to the `crawlAgents()` call so the SDK probes `list_authorized_properties` alongside registered agents - Promotion uses a positive PropertyIndex signal: `index.getAgentAuthorizations` must return non-empty `publisher_domains`; absent evidence triggers exponential backoff (1 d → 7 d → 30 d) via `recordSalesCandidateProbeFailure` - Migration 487 adds `probe_failure_count` and `next_probe_after` columns to `discovered_agents` with a partial index on `sales_candidate` rows - `getAllDiscoveredAgents` SELECT extended to include the new columns - Integration tests cover the full upsert/probe/promote/backoff lifecycle; brand-name references removed from test descriptions per repo conventions https://claude.ai/code/session_01TWcgPcFK8zNMcLiryCVzBH
Closed
4 tasks
bokelley
marked this pull request as ready for review
May 22, 2026 19:34
Contributor
There was a problem hiding this comment.
LGTM. Follow-ups noted below. Right shape: gates promotion on positive publisher_domains evidence rather than absence-of-error, and the candidate state lives in the same row that eventually becomes sales — no separate table to keep in sync.
Things I checked
- Migration 492 adds two columns to
discovered_agentsand a partial index gated onagent_type = 'sales_candidate'.discovered_agentsis a global directory (noaccount_id/tenant_id), so tenant-scoping doesn't apply.ADD COLUMN ... NOT NULL DEFAULT 0is metadata-only on PG 11+. Migration number 491 is the only gap in the 482–492 sequence — not a collision. upsertSalesCandidate(federated-index-db.ts:751-765) — CASE guard is correct: onlyNULLor'unknown'get overwritten;sales/creative/signals/buyerare preserved. Test atcrawler-publisher-properties-fanout.test.ts:478-494exercises the no-downgrade path.- TOCTOU between
getSalesCandidatesForProbe(crawler.ts:163) andpromoteSalesCandidateToSales/recordSalesCandidateProbeFailure(crawler.ts:227/230) is bounded byWHERE agent_type = 'sales_candidate'in both UPDATEs (federated-index-db.ts:793,812). A concurrent promote turns the second writer's UPDATE into a no-op. Safe, but load-bearing — don't remove that gate. - Candidate publisher_domains from the probe flow through populate. The
listDiscoveredAgents()walk atcrawler.ts:660has noagent_typefilter, sosales_candidaterows go throughindex.getAgentAuthorizationson the same pass. Comment update at L654-660 is accurate. - Changeset present (
.changeset/crawler-sales-candidate-probe.md). Empty front matter is the server-only convention used elsewhere in.changeset/. No wire-touching files, so nomajor/minor/patchbump required.
Follow-ups (non-blocking — file as issues)
- Backoff schedule prose drift. The docstring at
federated-index-db.ts:800says "first failure → 1 day, 1–2 failures → 7 days, 3+ → 30 days," which the SQL implements correctly via pre-increment CASE values. But the PR body, changeset, and migration header at492_*.sql:5all collapse it to "1 day → 7 days → 30 days," implying a three-step ladder. The actual ladder is 1d → 7d → 7d → 30d → 30d (two 7-day steps because< 3matches pre-increment values 1 and 2). Either tighten the changeset/migration prose to match, or change< 3to< 2. Lean toward fixing the prose — two 7-day retries before escalating to 30 is the more humane curve. - Empty-inventory probes accrue backoff.
crawler.ts:226treats a reachable agent that returnspublisher_domains: [](e.g., a freshly-onboarded agent with no publishers yet) as a probe failure and applies backoff. After three such "failures" the agent is parked for 30 days. Worth distinguishing reachable-but-empty from unreachable in a follow-up so newly-onboarded agents don't get exiled. - Crawler-level promotion test missing. The new test block at
crawler-publisher-properties-fanout.test.ts:450-573covers the DB state machine cleanly (insert, no-downgrade, due-filter, promote-resets, failure-increments, idempotency) but never exercisescrawler.ts:221-238— the wiring that reads PropertyIndex and decides promote-vs-backoff. A test that seeds PropertyIndex with apublisher_domainsentry for the candidate URL and asserts promotion happens would close that gap.
Minor nits (non-blocking)
- Protocol cast pattern.
(candidate.protocol as 'mcp' | 'a2a') || 'mcp'atcrawler.ts:170mirrors the existing pattern at L147 — fine to leave, but acoerceProtocol(s)helper would centralize the assumption. Notable that the column has no CHECK constraint, so anything could land there.
LGTM. Follow-ups noted below.
Contributor
|
The automated review encountered an issue (possibly reached max turns, timed out, or failed to post the final This is an automated message from the Argus AI review workflow. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #4846
Problem
Agents discovered via
authorization_type: 'publisher_properties'inadagents.jsonwere never included in the periodic sales-agent probe loop. The crawler fan-out (#4840) correctly persisted per-child-publisher rows, but the declaring manager agent itself was never re-probed vialist_authorized_propertiesin subsequent crawl cycles. That meant new child publishers added to a manager's delegation list were only discovered on a full re-crawl of the manager'sadagents.json— not via the periodic probe.Solution
1.
sales_candidateregistration (fanOutPublisherPropertiesAuthorizations)After writing the per-child rows, the fan-out helper now calls
upsertSalesCandidate(agentUrl, sourceDomain). The upsert uses aCASEguard so confirmed types (sales,creative, etc.) are never downgraded — onlyNULLorunknownrows get written assales_candidate. This path runs from all three entry points: full cycle, manager-revalidation queue, and single-domain refresh.2. Probe inclusion (
crawlAllAgents)Before calling
crawler.crawlAgents(), the crawler fetchessales_candidaterows viagetSalesCandidatesForProbe()(filtered bynext_probe_after <= NOW()or NULL) and appends them to theAgentInfo[]list. The SDK then probeslist_authorized_propertieson those agents alongside registered agents in the same call.3. Positive-signal promotion
Promotion requires
index.getAgentAuthorizations(agentUrl)returning non-emptypublisher_domainsfrom the in-memoryPropertyIndex— absence of an error is not sufficient evidence. Candidates that return no authorizations receive arecordSalesCandidateProbeFailurecall applying exponential backoff: 1 day → 7 days → 30 days.4. Migration 487
5.
getAllDiscoveredAgentsextendedThe general-purpose SELECT now includes
probe_failure_countandnext_probe_afterso callers get the fullDiscoveredAgentshape.Files changed
server/src/db/migrations/487_discovered_agents_sales_candidate_probe.sqlserver/src/db/federated-index-db.tsserver/src/federated-index.tsserver/src/crawler.tsserver/tests/integration/crawler-publisher-properties-fanout.test.ts.changeset/crawler-sales-candidate-probe.mdTest coverage
The new integration test block (
FederatedIndexDatabase — sales_candidate probe lifecycle) covers:upsertSalesCandidatecreates a row withsales_candidatetypesalesrowspromoteSalesCandidateToSalesupgrades the row and clears backoff staterecordSalesCandidateProbeFailureapplies correct backoff intervalsgetSalesCandidatesForProberespectsnext_probe_afterfilteringGenerated by Claude Code