fix(registry): require declared agent type + auto-backfill primary_brand_domain#4235
Merged
EmmaLouise2018 merged 2 commits intomainfrom May 8, 2026
Merged
Conversation
…and_domain Make agent type a required, owner-declared field at every registration surface, and auto-populate primary_brand_domain when an agent is registered against a profile that has none. Prevents the "registered agent invisible in /api/registry/operator" failure mode where a profile had a typed agent in dashboard but registry lookup returned member: null, agents: []. - save_agent (Addie): require type input enum (8 values, no 'unknown'), persist on insert and update on re-save. Behaviors.md intake now asks for type before save_agent — replaces the old "do not ask about agent type" rule. - POST /api/me/agents: 400 on missing/invalid/'unknown' type. PATCH validates type when present (omission preserves existing). - Mutation helper backfills primary_brand_domain atomically when null AND every agent agrees on the same hostname (after stripping www.). Conflicts are skipped — picking one would mis-key registry lookups. - Operator endpoint drops the silent || "unknown" fallback; out-of-enum values still serialize as "unknown" to keep the schema contract but a warn-level log fires so corrupt rows are visible. - OpenAPI: new MemberAgentTypeInput enum (sans 'unknown') marks type required on POST input; descriptions corrected.
This was referenced May 8, 2026
…ad schema Addresses Brian's review on #4235. - Add 7 integration tests in member-agents-api.test.ts pinning the new contract: POST 400 on missing/unknown/out-of-enum type, PATCH invalid_type with omission-preserves-existing, primary_brand_domain auto-backfill on null + unanimous hostname, no backfill on conflict, no overwrite when already set. - Update 14 existing POST sites in member-agents-api.test.ts and member-agents-auto-bootstrap.test.ts to declare type: 'sales' so they exercise the new gate cleanly rather than tripping it. - Tighten MemberAgentSchema.type from optional to required so the OpenAPI read shape matches what the operator route always emits. Follow-ups filed: #4236 (surface bulk-defaulted sales type to owners), #4237 (corrupt-row diagnostics sentinel on operator response).
12 tasks
EmmaLouise2018
added a commit
that referenced
this pull request
May 8, 2026
…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 was referenced May 8, 2026
Merged
bokelley
pushed a commit
that referenced
this pull request
May 8, 2026
Generated build artifacts reflecting schema changes already merged to main (#4235 agent type required, onboarding schema addition). Committed to keep dist/schemas in sync with source. https://claude.ai/code/session_01DafX6UtByExTqbPcJHXFnT
bokelley
pushed a commit
that referenced
this pull request
May 9, 2026
…e seed + read-side CTE) (#4252) 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.
bokelley
added a commit
that referenced
this pull request
May 9, 2026
… eval → dashboard, requeue endpoint (#4265) * fix(compliance): heartbeat pre-stamp TTL, dry_run correctness, manual eval → dashboard status, requeue endpoint Fixes three bugs reported in #4253 (comply re-runner stale status) plus adds a member-facing requeue workaround: 1. compliance-heartbeat: pre-stamp uses NOW()+30min lock TTL instead of NOW() so a mid-loop process crash re-queues within 30 min rather than blocking for the full check_interval (default 12 h). 2. complianceResultToDbInput: remove hardcoded dry_run:true; heartbeat paths now set dry_run:false explicitly so scheduled runs are marked authoritative. 3. evaluate_agent_quality: call complianceDb.recordComplianceRun() after agentContextDb.recordTest() so manual runs update the dashboard comply status immediately (dry_run:false so they count alongside heartbeat runs). 4. New POST /api/registry/agents/{url}/monitoring/requeue endpoint + dashboard "Requeue comply" button: clears last_checked_at so the agent is picked up on the next heartbeat cycle (~1 hour). Owner-auth + 60s per-agent rate limit. Refs #4253 https://claude.ai/code/session_01DafX6UtByExTqbPcJHXFnT * fix(compliance): requeueForHeartbeat upsert for first-run agents Bare UPDATE silently no-ops when agent has no existing row in agent_compliance_status. Switch to INSERT ... ON CONFLICT DO UPDATE so the requeue endpoint works even for agents that have never been through the heartbeat cycle. https://claude.ai/code/session_01DafX6UtByExTqbPcJHXFnT * chore(dist): compile member-agents and onboarding OpenAPI schemas Generated build artifacts reflecting schema changes already merged to main (#4235 agent type required, onboarding schema addition). Committed to keep dist/schemas in sync with source. https://claude.ai/code/session_01DafX6UtByExTqbPcJHXFnT --------- Co-authored-by: Claude <noreply@anthropic.com>
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.
Summary
save_agent(Addie MCP tool):typenow required in input schema (8-value enum:brand,rights,measurement,governance,creative,sales,buying,signals). Persists on insert; updates on re-save when an owner corrects an earlier wrong type.POST /api/me/agents: returns400 type is requiredon missing/invalid/'unknown'.PATCH /api/me/agents/:url: returns400 invalid_typeon invalid; omission preserves existing.member_profiles.primary_brand_domainatomically with the JSONB write (under the sameFOR UPDATErow lock) when the column is null AND every agent in the resulting array agrees on one hostname (after strippingwww.). Conflicts are skipped — picking one would mis-key registry lookups./api/registry/operator): drops silent|| "unknown"fallback. Out-of-enum values still serialize as"unknown"to preserveOperatorLookupResultSchema, but awarn-level log fires withdomain,url,storedType,profileSlugso ops can catch corrupt rows.MemberAgentTypeInputenum (sans'unknown');MemberAgentInputSchema.typenow required; read-sideMemberAgentSchema.typetightened from optional to required (matches what the operator route always emits).behaviors.md): "do not ask about agent type" replaced with "always ask, never guess." Owner declares; if the user describes capabilities, Addie may suggest a fit but the user must confirm before save.Why
Caught in production: a member registered an agent at
https://www.harvingupta.xyz/api/mcpand saw it correctly in/dashboard/agents, butGET /api/registry/operator?domain=harvingupta.xyzreturned{ member: null, agents: [] }. Two compounding gaps:typewas absent.save_agent's schema explicitly did not accept atypefield — it relied on a server-side capability-snapshot resolution that silently produced no value when the probe failed.POST /api/me/agentsacceptedPartial<AgentConfig>and never validatedtypeeither. The operator endpoint masked the missing field withtype: ac.type || "unknown", so bad data flowed straight through to the public response.primary_brand_domainwasNULL. Agent registration writesmember_profiles.agentsJSONB but never backfilledprimary_brand_domain, and the public operator lookup keys exact-match on that column. Result: a registered agent that the profile owner could see in the dashboard but no peer could discover via the registry.Fixing both gaps at once because they're tightly coupled — the type guess and the missing brand-domain backfill were each independently sufficient to make the agent invisible.
Behavior change
POST /api/me/agentsandsave_agentnow reject writes that don't declaretype. Audit of write surfaces:server/public/dashboard-agents.html) — redirects to Addie chat; Addie's updated intake script asks fortypebefore callingsave_agent. ✅ unaffectedsave_agentMCP tool — schema enforcestypeat the JSON-RPC layer; runtime check is the suspenders. ✅ unaffected for new conversations; existing prompts that paste-it-all need to include typePOST /api/me/agentsREST — any external caller posting{ url, name, visibility }withouttypewill start getting400. Quantify before merge: search downstream SDK callers (@adcp/client, internal storefront integrations) for unsettype. None found in this repo's tests; updated 14 test sites here to declaretype: 'sales'.PATCH /api/me/agents/:url— omittingtypepreserves the existing value (no behavior change for existing callers).Migration
Existing rows handled by a manual migration on the prod pod (deliberately out of
release_commandso it doesn't auto-fire on deploy):primary_brand_domainfrom the unanimous agent hostname.typetosales(per owner instruction).The bulk-default-to-
salesis a guess applied at migration time — the very pattern this PR forbids for new writes. Tracked as a follow-up: surface a "your agent's type was bulk-set, confirm or correct" banner on/dashboard/agentsfor the affected cohort. See #4236.What changed (quantified)
AgentTypevalues enforced at input;'unknown'reserved for server-side smuggle protection only.POST /api/me/agents,PATCH /api/me/agents/:url,save_agentMCP tool.MemberAgentSchema.typefrom optional → required (matches operator route behavior).||fallback removed; replaced with awarn-level structured log on corrupt rows.type(covers bothmember-agents-apiandmember-agents-auto-bootstrapintegration suites).Out of scope
salescohort to owners for confirmation. Migration shortcut, not a per-write bug; deferred to keep this PR's scope to code-only._diagnostics.type_sourcesentinel on operator-endpoint responses so consumers can distinguish "snapshot contradicted declaration" from "corrupt row." Wire-visible additive field; deferred to v6 / 4.0 track.Test plan
tsc --noEmit -p server/tsconfig.json— cleanvitest run server/tests/unit— only 5 pre-existing failures (taxonomy enum sync, missing storyboard yaml); confirmed againstmainby stashing changes; none touch modified filesnpm run build)type: 'sales'so they exercise the new gate cleanly rather than tripping ittests/integration/member-agents-api.test.ts:'unknown'(reserved server-side)'seller')primary_brand_domainfrom agent hostname when null + unanimousprimary_brand_domainwhen already settests/integration/member-agents-api.test.ts,tests/integration/agent-visibility-e2e.test.ts,tests/integration/member-agents-auto-bootstrap.test.tsGET /api/registry/operator?domain=<owner-brand>returns the agentPOST /api/me/agentswith notypereturns 400type is required