[SC-16888] Reconcile stale agents on sync to prune archived/deleted VM records#124
Conversation
…eted records After each sync pull from ValidMind, delete synced agent rows whose vm_cuid is no longer present in the response (archived or deleted in VM). - AgentsRepo.DeleteSyncedStaleForOrg: deletes synced agents for the given org whose vm_cuid is not in keepCUIDs. If keepCUIDs is empty, removes all synced agents for the org. Manually-created agents (empty vm_organization_cuid) are never touched. - AgentsRepo.ListVMCUIDsWithBindings: returns vm_cuids of agents with at least one managed_agent_binding row. These are added to keepCUIDs before pruning so their ON DELETE CASCADE binding config is preserved. - syncAgents now collects activeCUIDs from successful upserts, fetches boundCUIDs, and calls DeleteSyncedStaleForOrg with the union. If the bindings query fails, the prune step is skipped (safe degradation). Co-authored-by: Cursor <cursoragent@cursor.com>
|
Should we add a new column to the agents table to differentiate manually created agents vs synced? I'm concerned we could accidentally delete a manually made agent |
…rs-from-validmind
The vm_organization_cuid column already serves as the manual-vs-synced discriminator — manually-created agents have it set to "". DeleteSyncedStaleForOrg scopes its DELETE to rows where vm_organization_cuid = , so empty-cuid (manual) agents are never matched. A separate boolean column would be redundant given this invariant is already enforced at the insert site. |
Replaces the raw IN (SELECT ...) subquery with a squirrel-built JOIN, consistent with the rest of the repo's query builder usage. Co-authored-by: Cursor <cursoragent@cursor.com>
in that case should |
An empty orgCUID would match all manually-created agents (vm_organization_cuid = ""), wiping them. Return an error early instead. Co-authored-by: Cursor <cursoragent@cursor.com>
Thanks, added that check |
What and why?
Previously, archiving or deleting an inventory record in ValidMind left the corresponding agent row in Atryum's database indefinitely — the sync only upserted, never removed. The agent remained visible in the Atryum UI until a user changed the org or record type in Settings (which triggers
DeleteSynced).This PR adds reconciliation to every sync run: after upserting the active records returned from ValidMind, any synced agent for the configured org whose
vm_cuidis no longer in the pull is deleted. This covers records archived or deleted in ValidMind.Safety: agents with active managed agent bindings (
managed_agent_bindingsFK →agents.id ON DELETE CASCADE) are exempted from pruning — theirvm_cuidis added tokeepCUIDsbefore the delete so binding configuration is not accidentally destroyed.Companion PR in the backend repo adds the CRUD-event triggers that fire the sync automatically: validmind/backend#3330
How to test
What needs special review?
ListVMCUIDsWithBindingsuses a raw SQL query (agents WHERE id IN (SELECT DISTINCT agent_cuid FROM managed_agent_bindings)). This works for both SQLite and PostgreSQL dialects but bypasses the squirrel query builder — intentional for the subquery pattern.ListVMCUIDsWithBindingserrors, the entire prune step is skipped (logged). This is a safe degradation: stale agents linger until the next successful sync rather than risking accidental deletion.Dependencies, breaking changes, and deployment notes
validmind/backend: validmind/backend#3330 — adds the CRUD event triggers that call Atryum's sync endpoint automatically.Release notes
Archived or deleted ValidMind inventory records are now automatically removed from Atryum's agent list on the next sync, keeping the two systems consistent without manual intervention.
Made with Cursor