fix(registry): heartbeat picks up member-registered agents — write-side seed + read-side CTE#4252
Merged
Conversation
…e seed + read-side CTE) Agents registered via POST /api/me/agents or save_agent live in member_profiles.agents JSONB but never landed an agent_registry_metadata row. The compliance heartbeat's known_agents CTE unioned only discovered_agents and agent_registry_metadata, so member-registered agents were invisible to the heartbeat — agent_compliance_status stayed empty, /api/registry/agents/<url>/compliance returned status:"unknown" forever, regardless of the 12h cycle. Fix shape — both write-side and read-side, mirrors PR #4235. - Write-side: applyMemberAgentMutation and save_agent both upsert agent_registry_metadata atomically with the JSONB write. ON CONFLICT DO NOTHING preserves owner-customized lifecycle / interval / opt-out. - Read-side defense in depth: known_agents CTE in getAgentsDueForCheck gains a third leg unioning member_profiles.agents URLs. ORDER BY adds agent_url tiebreaker. 3 new integration tests pin the contract: POST seeds metadata when none exists, POST preserves customized metadata on re-register, getAgentsDueForCheck picks up an agent that lives only in member_profiles.agents (read-side CTE in isolation). Manual migration run on the pod backfills agent_registry_metadata rows for existing member-profile agents that have no metadata row.
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 part of the visibility-miss bug class fixed in #4235 (different table, same shape).
Summary
Agents registered via
POST /api/me/agentsorsave_agentlived inmember_profiles.agentsJSONB but never landed anagent_registry_metadatarow. The compliance heartbeat'sknown_agentsCTE unioned onlydiscovered_agentsandagent_registry_metadata, so member-registered agents were invisible to the heartbeat.agent_compliance_statusstayed empty,/api/registry/agents/<url>/compliancereturnedstatus: "unknown"forever regardless of the 12h cycle.Why
Caught on the same agent that motivated #4235 (
harvingupta.xyz/api/mcp). Pod audit confirmed:agent_registry_metadata0 rows,agent_compliance_status0 rows,discovered_agents0 rows — butagent_capabilities_snapshotandagent_health_snapshothad 1 row each (other probes ran). The heartbeat's enqueue query keys off the wrong source for member-registered agents.Fix shape — write-side seed + read-side CTE
applyMemberAgentMutation(the helper backingPOST /api/me/agentsandPATCH /api/me/agents/:url) bulk-upsertsagent_registry_metadatarows for every URL in the resulting agents array, atomically with the JSONB write.ON CONFLICT DO NOTHINGpreserves owner-customizedlifecycle_stage/check_interval_hours/compliance_opt_out. Thesave_agentMCP handler does the same upsert after its profile write; failures are logged atwarnand don't fail the registration.known_agentsCTE ingetAgentsDueForCheckgains a third leg unioningmember_profiles.agentsURLs. Any agent that slipped past the seed is still picked up by the heartbeat.ORDER BYaddska.agent_url ASCas a deterministic tiebreaker so two never-checked agents land in stable order across runs.Behavior change
Heartbeat will start picking up previously-orphaned member-profile agents on its next cycle — brief surge of compliance runs after deploy as the backlog drains. Per-agent rate limits and the heartbeat's per-run
limitcap (default 10) bound the surge.Migration
Manual backfill script for existing rows (out of
release_commandso it doesn't auto-fire on deploy):Refs
Test plan
tsc --noEmit -p server/tsconfig.jsoncleanmember-agents-api.test.ts:agent_registry_metadatarow when none existslifecycle_stage = 'testing'andcheck_interval_hours = 24survive)getAgentsDueForCheckpicks up an agent that lives only inmember_profiles.agents(read-side CTE in isolation, with metadata row deliberately absent)/api/registry/agents/:url/compliancereturns a real verdict (not"unknown")