feat(knowledge): conflict resolution — agent annotates, nightly executor applies (agents' KB #4)#226
Merged
Merged
Conversation
…tor applies (agents' KB #4, resolution) Closes the route-the-findings loop: the retrieving agent (which has live context) records a VERDICT; a trusted nightly executor applies it. Judgment stays with the grounded agent; the KB never re-judges. No LLM — dismiss/supersede only (merge is the later LLM step). Why a separate table: article_links are immutable by design, so the verdict lives in a new mutable conflict_resolutions row (one per canonical pair; re-annotation upserts = last-write-wins, so the freshest grounded judgment governs). Flow: - annotate_conflict/3 (agent, non-destructive): dismiss | supersede | merge, with authoritative_article_id (winner) for supersede/merge, classification, evidence, confidence. Canonicalizes the unordered pair. dismiss takes effect immediately (pair drops from the queue + get_article surface); supersede/merge defer. - execute_conflict_resolutions/2 (nightly, in KnowledgeLintWorker, system-privileged): applies only high-confidence supersedes — reuses create_link(:supersedes) to link winner->loser and transition the loser to :superseded (reversible + audited). Bounded. Low-confidence / merge left for review / the LLM step. - Queue (list_potential_conflicts) and the get_article potential_conflicts field now exclude resolved pairs — the queue shows only OPEN conflicts. Surface: POST /api/v1/knowledge/conflicts/resolve (agent+); MCP v2.27.0 knowledge_resolve_conflict. RLS on the new table. Tests (+9 Elixir, +1 MCP): dismiss-immediate + queue drop; high-conf supersede applied (loser superseded + supersedes link); sub-high not applied; last-write-wins across pair order; get_article drops resolved; supersede requires authoritative; controller dismiss/supersede/422; MCP plumbing. Full gate green (3057 tests, dialyzer, credo).
…er CI break) Unrelated to #4 — surfaced by this PR's CI running after the UTC month rollover. audit_log is time-partitioned; the create_audit_log migration seeds a fixed window (its month + 3 = through 2026-06), and the test DB only runs migrations. Once the wall clock passes that window, every audit insert fails with 'no partition of relation audit_log' — blocking ALL PRs from 2026-07-01. Prod is unaffected: the nightly AuditPartitionWorker keeps the window ahead. Expose AuditPartitionWorker.ensure_partitions/0 (idempotent CREATE IF NOT EXISTS for current + lookahead) and call it in test_helper, so the suite no longer depends on when the migration happened to run.
Refines the previous commit: fixed-date audit rows (e.g. ~U[2026-06-24]) land in a PAST month relative to a fresh CI migrate, so ensure a back-window too (back: 12). And run the DDL via Sandbox.unboxed_run so the CREATE TABLE commits — a plain query in test_helper executes inside the sandbox transaction and is rolled back before any test sees it. Verified on a freshly reset test DB: audit keyset tests + full suite green (3057).
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.
What — route-the-findings step 1: conflict resolution
#224/#225 detect and surface conflicts. This closes the loop: the retrieving agent (which has the live context the nightly job lacks) records a verdict; a trusted nightly executor applies it. Judgment stays with the grounded agent — the KB never re-judges. No LLM (
dismiss/supersedeonly;mergeis the later LLM step).Design
conflict_resolutionstable —article_linksare immutable by design, so the verdict lives in a new mutable row. One row per canonical (sorted) pair; re-annotation upserts = last-write-wins, so the freshest grounded judgment governs.annotate_conflict/3(agent, non-destructive → agent role):dismiss|supersede|merge, withauthoritative_article_id(winner) for supersede/merge, plusclassification/evidence/confidence.dismisstakes effect immediately (pair drops from the queue + the article surface); supersede/merge defer.execute_conflict_resolutions/2(nightly, inKnowledgeLintWorker, system-privileged): applies only high-confidence supersedes — reusescreate_link(:supersedes)to link winner→loser and transition the loser to:superseded(reversible + audited). Bounded. Lower confidence /mergeleft for review / the LLM step.get_articlefield now exclude resolved pairs — the conflict queue shows only OPEN conflicts.This is the division we agreed on: agent judges (cheap, in-context, non-destructive), privileged batch executes (reversible-only, above a confidence bar), humans get the rest.
Surface
POST /api/v1/knowledge/conflicts/resolve(agent+).knowledge_resolve_conflict. RLS on the new table.Tests (+9 Elixir, +1 MCP)
dismiss-immediate + queue-drop; high-conf supersede applied (loser
:superseded+:supersedeslink created); sub-high not applied; last-write-wins across pair order;get_articledrops resolved; supersede requires authoritative; controller dismiss/supersede/422; MCP plumbing.Full gate green: format, credo --strict, dialyzer, 3057 tests, 0 failures; 53 MCP tests. Migration runs on deploy via
release_command.What's still open (honest)
mergedisposition is recorded but not executed — that's US-20.1: Documentation page with setup guides #4 step 2 (the isolated LLM-synthesis increment, drafts-only, behind the confidence gate).