Skip to content

feat(memory): typed confidence and symmetric conflict provenance (v6.6.0) - #208

Merged
CryptoJones merged 1 commit into
mainfrom
feat/confidence-conflicts
Aug 2, 2026
Merged

feat(memory): typed confidence and symmetric conflict provenance (v6.6.0)#208
CryptoJones merged 1 commit into
mainfrom
feat/confidence-conflicts

Conversation

@CryptoJones

Copy link
Copy Markdown
Owner

Closes #195.

The gap

An OMI note could express exactly one provenance fact: "this replaced that" (Supersedes:). That is a clean, ordered replacement. Real recall failures aren't clean — two notes disagree, neither has been reviewed, and the agent trusts whichever one the ranker surfaced. There was no way to say "these disagree" or "this was never verified".

Two fields, not a ledger

The issue explicitly scoped this away from claude-obsidian's two-ledger system (authority tiers, independent-source requirements for high-risk claims, 1,351 lines). omind is an agent's memory, not a citation manager. So:

  • Confidence: high|medium|low — absent means unknown, which is every note that already exists. An unrecognised value is dropped, not raised: this arrives from hand-edited Markdown and from mesh peers running older code, and a typo in one note must never render that note unreadable.
  • Conflicts with: [[Other]]symmetric in effect even when only one side declares it. The point of the field is that the agent sees the disagreement, so whichever note retrieval happens to surface must carry the warning. Writing it on both is good hygiene, not a requirement.

Both round-trip through Markdown, CLI (--confidence, --conflicts-with), MCP (create-note/edit-note), and the mesh merge driver, exactly as Supersedes: does. Partial edits no longer clear them (same inheritance rule as rev/created).

Retrieval surfaces conflicts; it does not resolve them

Both notes come back marked with the other's name, and ranking expresses no preference between them. Picking a winner would recreate the bug — the agent silently trusting one side.

Confidence: low gets a gentle 0.8 penalty so a comparable verified note wins a tie. Deliberately nothing like the 0.35 superseded penalty: low confidence is not obsolescence, and a hedged memory is still worth recalling.

recall-note emits confidence, conflicts_with, and a warning naming the other note — only when declared, so a note without these fields costs exactly the tokens it did before.

Lint

  • conflict-broken (error) — points at no note.
  • conflict-one-sided (info) — the other side never acknowledged it. Retrieval treats a one-sided claim as binding on both notes, so lint says so out loud rather than leaving it implicit.
  • conflict-self (warn).

Tests

10 new, across four suites: round-trip; absent fields render nothing (every pre-existing note untouched); an unknown confidence is dropped but leaves the note readable; a partial edit doesn't clear provenance; low confidence loses a tie but stays recalled; a one-sided conflict surfaces on both notes; recall-note's warning and its absence; and the three lint cases.

Migration

Search index schema → v5 for the two new columns. It rebuilds itself on first use; nothing to do.

Gates

ruff check . · mypy src (strict) · pytest (866 passed, 1 skipped) · pip-audit — green locally.

🤖 Generated with Claude Code

A note could express exactly one provenance fact — "this replaced that",
through `Supersedes:`. That is a clean, ordered replacement. Real recall
failures are messier: two notes disagree, neither has been reviewed, and the
agent trusts whichever one the ranker happened to surface. There was no way to
say "these disagree" or "this was never verified".

Two optional fields, round-tripping through Markdown, CLI, MCP, and the mesh
merge driver exactly as Supersedes does:

- `Confidence: high|medium|low`. Absent means unknown — which is every note
  that already exists, so absence stays the default. An unrecognised value is
  dropped rather than raising: it arrives from hand-edited Markdown and from
  mesh peers on older code, and a typo must never make a note unreadable.
- `Conflicts with: [[Other]]`, symmetric in effect even when only one side
  declares it. The whole point is that the agent sees the disagreement, so
  whichever note retrieval surfaces has to carry the warning.

Retrieval surfaces conflicts, it does not resolve them: both notes come back
marked with the other's name and no ranking preference between them. Low
confidence gets a gentle 0.8 penalty so a comparable verified note wins a tie —
deliberately nothing like the 0.35 superseded penalty, because low confidence
is not obsolescence.

`recall-note` emits the fields and a warning naming the other note, but only
when the note declares them, so notes without them cost what they always did.
Lint reports conflicts that point at nothing and conflicts the other side never
acknowledged.

Scoped as the issue asked: two fields, not a research-grade two-ledger system
with authority tiers and independent-source requirements. omind is an agent's
memory, not a citation manager.

Closes #195.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added optional confidence levels and conflict links to notes.
    • Confidence and conflict metadata now round-trip through Markdown, CLI, MCP, and mesh merges.
    • Search results display conflict metadata, surface linked conflicts symmetrically, and gently down-rank low-confidence notes.
    • Recall responses warn when a note has a conflict.
  • Bug Fixes

    • Added lint diagnostics for self-conflicts, missing targets, and one-sided conflict declarations.
  • Documentation

    • Updated note format, retrieval guidance, backlog, and changelog for version 6.6.0.

Walkthrough

The change adds optional Confidence and Conflicts with note metadata. The fields round-trip through Markdown, CLI, MCP, merge, indexing, and retrieval. Search penalizes low-confidence notes, conflicts appear symmetrically, and lint reports invalid declarations.

Changes

Provenance-aware notes

Layer / File(s) Summary
Note metadata and input flows
src/omind/store.py, src/omind/cli.py, src/omind/server.py, src/omind/merge.py, tests/test_store.py, README.md
NoteFields and NoteSummary support normalized confidence and conflict metadata. Markdown, CLI, MCP, partial updates, and merge operations preserve the fields.
Indexing and retrieval behavior
src/omind/searchindex.py, src/omind/recall.py, tests/test_searchindex.py, tests/test_server.py
Search schema v5 stores the new fields, applies a low-confidence penalty, resolves conflicts symmetrically, and exposes metadata and warnings in recall results.
Conflict validation and release support
src/omind/lint.py, tests/test_lint.py, docs/retrieval.md, BACKLOG.md, CHANGELOG.md, pyproject.toml, src/omind/__init__.py
Linting reports self-conflicts, broken targets, and one-sided declarations. Documentation and version metadata describe the 6.6.0 behavior.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant NoteStore
  participant SearchIndex
  participant Recall
  Client->>NoteStore: create or edit note metadata
  NoteStore->>SearchIndex: persist confidence and conflict fields
  SearchIndex->>SearchIndex: rank and resolve conflict metadata
  SearchIndex->>Recall: return enriched search hit
  Recall->>Client: return provenance fields and conflict warning
Loading

Possibly related PRs

  • CryptoJones/omind#182: Overlaps in search indexing, note storage, merging, linting, recall, and retrieval documentation.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 55.81% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: typed confidence and symmetric conflict provenance in v6.6.0.
Description check ✅ Passed The description directly explains the implemented confidence, conflict, retrieval, lint, migration, and testing changes.
Linked Issues check ✅ Passed The changes implement issue #195 requirements across storage, interfaces, merging, retrieval, ranking, linting, and index migration.
Out of Scope Changes check ✅ Passed The documentation, version updates, tests, and implementation changes remain related to the linked issue objectives.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/confidence-conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@CHANGELOG.md`:
- Line 10: Add a blank line immediately after each changelog heading, including
“Added” and “Changed”, to satisfy markdownlint MD022 while preserving the
existing entries.

In `@src/omind/lint.py`:
- Around line 248-280: Update the conflict validation around ids_to_note and the
valid-target branch so every target recognized by known, including archived and
subfolder notes from known_extra, is loaded into the reciprocal lookup while
remaining excluded from the outer lint-source iteration. Ensure valid targets
without reciprocal declarations can reach the existing conflict-one-sided
validation, and add a regression test covering an active note targeting an
archived or subfolder note.

In `@src/omind/searchindex.py`:
- Around line 138-143: Ensure every search path returns provenance: update
src/omind/searchindex.py lines 138-143 in _listing to populate confidence and
resolve conflicts_with symmetrically; update src/omind/store.py lines 1040-1048
in _scan_search to provide the same provenance when the index returns None; add
tests in tests/test_searchindex.py lines 476-509 covering OMI_INDEX_DISABLE=1
and index-failure fallback behavior.

In `@src/omind/store.py`:
- Around line 607-610: Update the note-rendering logic around the confidence
output to pass f.confidence through _clean_confidence before deciding whether to
append the Confidence field, so unsupported values are omitted while valid
normalized values render. Extend test_unknown_confidence_is_dropped_not_fatal to
assert the raw note does not contain “Confidence:”.
- Around line 1356-1362: Update the provenance inheritance logic in the note
edit flow around fields.confidence and fields.conflicts_with to distinguish
omitted fields from explicitly supplied empty strings. Preserve current values
only when the caller omitted each field (using the existing None-based presence
semantics), while allowing explicit empty strings to clear them; add MCP
coverage verifying that clearing each field persists.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 27c01de2-aefd-4c6f-acd7-cf5f4bcb55eb

📥 Commits

Reviewing files that changed from the base of the PR and between f98936d and 536576d.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (17)
  • BACKLOG.md
  • CHANGELOG.md
  • README.md
  • docs/retrieval.md
  • pyproject.toml
  • src/omind/__init__.py
  • src/omind/cli.py
  • src/omind/lint.py
  • src/omind/merge.py
  • src/omind/recall.py
  • src/omind/searchindex.py
  • src/omind/server.py
  • src/omind/store.py
  • tests/test_lint.py
  • tests/test_searchindex.py
  • tests/test_server.py
  • tests/test_store.py
📜 Review details
⏰ Context from checks skipped due to timeout. (10)
  • GitHub Check: test (ubuntu-latest, 3.14)
  • GitHub Check: test (macos-latest, 3.14)
  • GitHub Check: test (ubuntu-latest, 3.12)
  • GitHub Check: test (windows-latest, 3.10)
  • GitHub Check: test (ubuntu-latest, 3.10)
  • GitHub Check: test (ubuntu-latest, 3.13)
  • GitHub Check: test (macos-latest, 3.10)
  • GitHub Check: test (windows-latest, 3.14)
  • GitHub Check: test (ubuntu-latest, 3.11)
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{py,md}

📄 CodeRabbit inference engine (AGENTS.md)

For retrieval changes, consult docs/retrieval.md and preserve the documented search-index design: FTS5/BM25, chunk vectors, wikilink graph resolution, and Reciprocal Rank Fusion.

Files:

  • README.md
  • src/omind/__init__.py
  • CHANGELOG.md
  • src/omind/merge.py
  • docs/retrieval.md
  • BACKLOG.md
  • tests/test_server.py
  • src/omind/recall.py
  • tests/test_lint.py
  • src/omind/cli.py
  • src/omind/lint.py
  • tests/test_searchindex.py
  • src/omind/server.py
  • tests/test_store.py
  • src/omind/store.py
  • src/omind/searchindex.py
**/*.md

📄 CodeRabbit inference engine (AGENTS.md)

Documentation files must include the specified Nebraska footer; the README must use the centered banner version.

Files:

  • README.md
  • CHANGELOG.md
  • docs/retrieval.md
  • BACKLOG.md
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Treat Markdown notes as the source of truth; keep indexes, caches, and vectors in paths.state_dir(), never in the vault.
Retrieval must fail open: every search layer returns None on errors and callers fall back to the older search path. Test failure branches as well as successful retrieval.
All note writes must go through OmiStore; external writers should use notes.upsert_note. Preserve flocking, atomic rename, Lamport Rev: stamping, and soft deletes. Deletes archive with Disabled: true; only omind mesh purge removes notes permanently.
Use OmiStore.safe_name for every note read and write; never bypass it because path traversal must remain impossible.
Keep store.py framework-free: it must not depend on FastAPI or MCP; both the CLI and web app build on it.
Credential notes must be de-prioritized in search and gate suggestions unless the query concerns credentials, using retrieve._CREDENTIAL_PENALTY. The gate must never steer agents toward secrets notes.
MCP tools must never return unbounded output. Every list-shaped tool must paginate with limit, offset, total, and has_more through server._page.
Treat index.md and Memory Template.md as scaffolding rather than memories; reading them must not clear the consult gate. Use paths.NON_CONSULT_FILENAMES.
search() must refresh the index on every call so results remain correct after another process writes a note.
Recency is only a re-ranking leg: it may reorder notes matched by content legs but must never add otherwise-unmatched notes.
Do not replace lint.py's independent full-vault scanning with index wikilink handling; the index does not strip code fences, while lint.py does.
link_targets() must preserve the author’s link casing for dangling-link reports; only resolution may lowercase links.
Never mutate a NoteSummary returned from _cached_summary; it is shared cached state. Use dataclasses.replace, as in store._indexed_search.
Coerce embedding results through `se...

Files:

  • src/omind/__init__.py
  • src/omind/merge.py
  • tests/test_server.py
  • src/omind/recall.py
  • tests/test_lint.py
  • src/omind/cli.py
  • src/omind/lint.py
  • tests/test_searchindex.py
  • src/omind/server.py
  • tests/test_store.py
  • src/omind/store.py
  • src/omind/searchindex.py
tests/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Retrieval changes must test both indexed search and the fallback path with OMI_INDEX_DISABLE=1, including failure behavior. MCP list tools must remain bounded, covered by test_every_list_tool_is_bounded.

Files:

  • tests/test_server.py
  • tests/test_lint.py
  • tests/test_searchindex.py
  • tests/test_store.py
🪛 markdownlint-cli2 (0.23.1)
CHANGELOG.md

[warning] 10-10: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 40-40: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)

🔇 Additional comments (15)
src/omind/lint.py (1)

54-54: LGTM!

Also applies to: 193-193

tests/test_lint.py (1)

11-11: LGTM!

Also applies to: 193-216

docs/retrieval.md (1)

47-56: LGTM!

BACKLOG.md (1)

78-82: LGTM!

pyproject.toml (1)

3-3: LGTM!

src/omind/__init__.py (1)

5-5: LGTM!

src/omind/store.py (1)

183-188: LGTM!

Also applies to: 236-237, 268-295, 498-568

src/omind/cli.py (1)

341-351: LGTM!

Also applies to: 1331-1332

src/omind/server.py (1)

267-270: LGTM!

Also applies to: 281-296, 321-346

src/omind/merge.py (1)

271-272: LGTM!

tests/test_store.py (1)

830-853: LGTM!

Also applies to: 864-871

README.md (1)

225-230: LGTM!

src/omind/searchindex.py (1)

62-83: LGTM!

Also applies to: 201-234, 695-714, 838-838, 1037-1093, 1140-1160, 1193-1223, 1316-1326

src/omind/recall.py (1)

84-105: LGTM!

tests/test_server.py (1)

438-459: LGTM!

Comment thread CHANGELOG.md

## [6.6.0] - 2026-08-02

### Added

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add a blank line after each heading.

markdownlint-cli2 reports MD022 because ### Added and ### Changed have no blank line below them. Add one blank line after each heading.

Proposed fix
 ### Added
+
 - **Two optional provenance fields on a note: `Confidence:` and

 ### Changed
+
 - Ranking: `Confidence: low` applies a gentle 0.8 penalty, so a comparable

Also applies to: 40-40

🧰 Tools
🪛 markdownlint-cli2 (0.23.1)

[warning] 10-10: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CHANGELOG.md` at line 10, Add a blank line immediately after each changelog
heading, including “Added” and “Changed”, to satisfy markdownlint MD022 while
preserving the existing entries.

Source: Linters/SAST tools

Comment thread src/omind/lint.py
Comment on lines +248 to +280
# `Conflicts with:` is symmetric in meaning but written on one note at a
# time, so the two ways it goes wrong are a target that does not exist and
# a claim the other side never acknowledged. Retrieval treats a one-sided
# claim as binding on both notes; lint says so out loud.
declared: dict[str, str] = {}
ids_to_note: dict[str, str] = {}
for n in notes:
for note_id in n.ids:
ids_to_note[note_id] = n.path.name
for n in notes:
claim = n.fields.conflicts_with.strip()
if not claim:
continue
target = claim.strip("[]").split("|", 1)[0].split("#", 1)[0].strip().lower()
if target in n.ids:
issues.append(
LintIssue(
"conflict-self", "warn", n.path.name, "`Conflicts with:` points at itself"
)
)
continue
if target not in known:
issues.append(
LintIssue(
"conflict-broken",
"error",
n.path.name,
f"`Conflicts with: [[{claim}]]` resolves to no note",
)
)
continue
if resolved := ids_to_note.get(target):
declared[n.path.name] = resolved

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Validate reciprocity for every valid conflict target.

known includes archived and subfolder note IDs through known_extra, but ids_to_note contains only active top-level notes. If an active note conflicts with an archived or subfolder note, the target is valid, but ids_to_note.get(target) returns nothing. The code then emits neither conflict-broken nor conflict-one-sided, even when the target has no reciprocal declaration.

Load conflict metadata for valid non-source targets into the reciprocal lookup. Keep those notes excluded as lint sources. Add a regression test for this path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/omind/lint.py` around lines 248 - 280, Update the conflict validation
around ids_to_note and the valid-target branch so every target recognized by
known, including archived and subfolder notes from known_extra, is loaded into
the reciprocal lookup while remaining excluded from the outer lint-source
iteration. Ensure valid targets without reciprocal declarations can reach the
existing conflict-one-sided validation, and add a regression test covering an
active note targeting an archived or subfolder note.

Comment thread src/omind/searchindex.py
Comment on lines +138 to +143
#: The note this one declares (or is declared to be) in conflict with, if
#: any. Surfaced so an agent sees a disagreement instead of silently
#: trusting whichever side the ranker happened to put first (#195).
conflicts_with: str = ""
#: "high" | "medium" | "low"; "" when the note declares none.
confidence: str = ""

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Return provenance on every search path.

Ranked indexed hits carry provenance. Empty-query indexed listings create default Hit values. Index-disabled fallback searches create default NoteSummary values. Therefore search-vault hides confidence and conflict metadata when callers browse by tag or when retrieval fails open. The fallback also cannot surface one-sided conflicts symmetrically.

  • src/omind/searchindex.py#L138-L143: Populate confidence and resolved conflicts_with in _listing.
  • src/omind/store.py#L1040-L1048: Populate equivalent symmetric provenance during _scan_search when the index returns None.
  • tests/test_searchindex.py#L476-L509: Add coverage with OMI_INDEX_DISABLE=1 and an index-failure path.

As per coding guidelines, “Retrieval changes must test both indexed search and the fallback path with OMI_INDEX_DISABLE=1, including failure behavior.”

📍 Affects 3 files
  • src/omind/searchindex.py#L138-L143 (this comment)
  • src/omind/store.py#L1040-L1048
  • tests/test_searchindex.py#L476-L509
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/omind/searchindex.py` around lines 138 - 143, Ensure every search path
returns provenance: update src/omind/searchindex.py lines 138-143 in _listing to
populate confidence and resolve conflicts_with symmetrically; update
src/omind/store.py lines 1040-1048 in _scan_search to provide the same
provenance when the index returns None; add tests in tests/test_searchindex.py
lines 476-509 covering OMI_INDEX_DISABLE=1 and index-failure fallback behavior.

Source: Coding guidelines

Comment thread src/omind/store.py
Comment on lines +607 to +610
if f.confidence:
out.append(f"- Confidence: {f.confidence}")
if f.conflicts_with:
out.append(f"- Conflicts with: {f.conflicts_with}")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Normalize confidence before rendering.

Line 607 writes any nonempty direct API value. create-note accepts free text, so an unsupported value is persisted as an invalid Confidence: field even though _clean_confidence defines that unsupported values must be dropped.

Render only _clean_confidence(f.confidence). Extend test_unknown_confidence_is_dropped_not_fatal to assert that the raw note omits Confidence:.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/omind/store.py` around lines 607 - 610, Update the note-rendering logic
around the confidence output to pass f.confidence through _clean_confidence
before deciding whether to append the Confidence field, so unsupported values
are omitted while valid normalized values render. Extend
test_unknown_confidence_is_dropped_not_fatal to assert the raw note does not
contain “Confidence:”.

Comment thread src/omind/store.py
Comment on lines +1356 to +1362
# Same inheritance for the provenance fields (#195): a partial edit
# that carries neither must not silently clear a note's confidence
# or drop a recorded conflict.
if not fields.confidence:
fields.confidence = current.confidence
if not fields.conflicts_with:
fields.conflicts_with = current.conflicts_with

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Allow explicit provenance clearing.

Line 1359 treats "" as an omitted value. server.edit_note distinguishes omission with None, then assigns an explicit empty string to clear a field. This branch restores the old value, so MCP clients cannot clear confidence or conflicts_with.

Track field presence separately from field value. Preserve these fields only when the caller omitted them. Add MCP coverage for clearing each field.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/omind/store.py` around lines 1356 - 1362, Update the provenance
inheritance logic in the note edit flow around fields.confidence and
fields.conflicts_with to distinguish omitted fields from explicitly supplied
empty strings. Preserve current values only when the caller omitted each field
(using the existing None-based presence semantics), while allowing explicit
empty strings to clear them; add MCP coverage verifying that clearing each field
persists.

@CryptoJones
CryptoJones merged commit 32d2c9e into main Aug 2, 2026
16 checks passed
@CryptoJones
CryptoJones deleted the feat/confidence-conflicts branch August 2, 2026 11:54
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.

Memory shape: typed confidence + symmetric 'Conflicts with' provenance on notes

1 participant