Skip to content

feat(knowledge): conflict resolution — agent annotates, nightly executor applies (agents' KB #4)#226

Merged
mkreyman merged 3 commits into
masterfrom
knowledge-conflict-resolution
Jul 1, 2026
Merged

feat(knowledge): conflict resolution — agent annotates, nightly executor applies (agents' KB #4)#226
mkreyman merged 3 commits into
masterfrom
knowledge-conflict-resolution

Conversation

@mkreyman

@mkreyman mkreyman commented Jul 1, 2026

Copy link
Copy Markdown
Owner

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/supersede only; merge is the later LLM step).

Design

  • Separate conflict_resolutions tablearticle_links are 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, with authoritative_article_id (winner) for supersede/merge, plus classification / evidence / confidence. dismiss takes effect immediately (pair drops from the queue + the 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. Lower confidence / merge left for review / the LLM step.
  • Queue + get_article field 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+).
  • 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 created); 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: format, credo --strict, dialyzer, 3057 tests, 0 failures; 53 MCP tests. Migration runs on deploy via release_command.

What's still open (honest)

  • merge disposition 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).
  • The tool-description pointers + a wiki playbook article (the "when you see a conflict, here's the protocol" instruction) are a small follow-up.

mkreyman added 3 commits June 30, 2026 18:13
…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).
@mkreyman mkreyman merged commit 1c199a5 into master Jul 1, 2026
9 checks passed
@mkreyman mkreyman deleted the knowledge-conflict-resolution branch July 1, 2026 00:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant