Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,11 +129,8 @@ in [BACKLOG.md](BACKLOG.md). Highest leverage first:
matches at ranks 4–10. Local only; never an API call on the query path.
3. **#176, #175** — cheap, contained perf/size wins.

**Two items need the user's decision before any code:**
**One item needs the user's decision before any code:**

Comment on lines +132 to 133

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

Reconcile the handoff status with BACKLOG.md.

This hunk removes #177 from the decision list, but AGENTS.md still says #174 and #177 were left open at Line 151 through Line 153. It also keeps #172 as the remaining decision item, while BACKLOG.md marks #172 as done at Lines 36-40. Update the handoff to list only active decisions and shipped work consistently.

🤖 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 `@AGENTS.md` around lines 132 - 133, Reconcile the handoff status across
AGENTS.md and BACKLOG.md: update the decision list and the references around
issues `#172`, `#174`, and `#177` so only active decisions remain, and shipped items
are marked consistently with BACKLOG.md. Remove stale open-status references and
do not retain `#172` as a decision item if BACKLOG.md marks it done.

- **#177** (fold the four `graph-*` tools into one) renames tools referenced by
the OMI vault's `Playbook.md`, the managed `omind` skill, and other fleet
machines. Coordinated fleet change, not a local one.
- **#172** (`omind consolidate`) merges notes. Propose-and-review only — a wrong
merge destroys memory that exists nowhere else.

Expand Down
7 changes: 3 additions & 4 deletions BACKLOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,10 @@ _No open items._

### Efficiency

- [ ] **Remove the deprecated `graph-*` MCP compatibility aliases after one release**
- [x] **Remove the deprecated `graph-*` MCP compatibility aliases after one release**
([#181](https://github.com/CryptoJones/omind/issues/181)) — _chore (tokens)_ —
`graph-path`, `graph-orphans`, `graph-dangling`, and `graph-stats` remain for
one bridge release while fleet clients refresh; remove them in the following
release. `graph-neighbors` stays.
Removed `graph-path`, `graph-orphans`, `graph-dangling`, and `graph-stats`
after the 5.0 bridge release; `graph-neighbors` stays.

## Not planned

Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Removed
- Remove the deprecated `graph-path`, `graph-orphans`, `graph-dangling`, and
`graph-stats` MCP compatibility aliases after the 5.0 bridge release. Use the
unified `graph(op=path|orphans|dangling|stats)` tool; `graph-neighbors` stays.

### Changed
- `omind bench` now reports the exposed MCP tool count and serialized schema
token estimate so future surface changes remain measurable.
Comment on lines +10 to +17

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 blank lines after the new headings.

markdownlint-cli2 reports MD022 at Line 10 and Line 15 because each heading is followed immediately by a list. Add one blank line after ### Removed and ### Changed.

Proposed fix
 ### Removed
+
 - Remove the deprecated `graph-path`, `graph-orphans`, `graph-dangling`, and
   `graph-stats` MCP compatibility aliases after the 5.0 bridge release. Use the
   unified `graph(op=path|orphans|dangling|stats)` tool; `graph-neighbors` stays.
 
 ### Changed
+
 - `omind bench` now reports the exposed MCP tool count and serialized schema
   token estimate so future surface changes remain measurable.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
### Removed
- Remove the deprecated `graph-path`, `graph-orphans`, `graph-dangling`, and
`graph-stats` MCP compatibility aliases after the 5.0 bridge release. Use the
unified `graph(op=path|orphans|dangling|stats)` tool; `graph-neighbors` stays.
### Changed
- `omind bench` now reports the exposed MCP tool count and serialized schema
token estimate so future surface changes remain measurable.
### Removed
- Remove the deprecated `graph-path`, `graph-orphans`, `graph-dangling`, and
`graph-stats` MCP compatibility aliases after the 5.0 bridge release. Use the
unified `graph(op=path|orphans|dangling|stats)` tool; `graph-neighbors` stays.
### Changed
- `omind bench` now reports the exposed MCP tool count and serialized schema
token estimate so future surface changes remain measurable.
🧰 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)


[warning] 15-15: 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` around lines 10 - 17, Add a blank line immediately after the
“### Removed” and “### Changed” headings in the changelog, keeping their
existing list content unchanged.

Source: Linters/SAST tools

- **A genuine preflight miss auto-clears the per-turn gate instead of forcing a
manual consult.** Previously, when `retrieve.relevant_titles` found nothing
relevant to the turn's task, the gate stayed armed and demanded a
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,7 @@ questions that previously matched nothing now return ranked answers.
```bash
omind search "why did release signing fail" --explain
omind reindex --index-only # refresh the derived index (search does this too)
omind bench # latency and token cost on your own vault
omind bench # latency, MCP schema size, and token cost

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

Document both MCP surface measurements.

omind bench reports MCP tools exposed and MCP tool schemas. The command comment mentions schema size but omits the tool count. Update the description so it matches the benchmark output.

Proposed fix
-omind bench                    # latency, MCP schema size, and token cost
+omind bench                    # latency, MCP tool count/schema size, and token cost
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
omind bench # latency, MCP schema size, and token cost
omind bench # latency, MCP tool count/schema size, and token cost
🤖 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 `@README.md` at line 408, Update the README command comment for omind bench to
mention both MCP measurements: the number of tools exposed and the MCP tool
schema size, while preserving the existing latency and token cost descriptions.

```

The index lives in the state directory, never in the vault — it is disposable
Expand Down
19 changes: 14 additions & 5 deletions src/omind/bench.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,18 @@
* **latency** — build the index, refresh it incrementally, and run queries both
through the index and through the pre-index full-vault scan, so the speedup is
a measurement rather than a claim.
* **tokens** — the size of what a session actually pays for: the SessionStart
capsule, one bounded recall, and the paged-versus-unpaged listing payload that
used to be ~87k tokens in a single MCP tool result.
* **tokens** — the size of what a session actually pays for: the MCP tool schema,
SessionStart capsule, one bounded recall, and the paged-versus-unpaged listing
payload that used to be ~87k tokens in a single MCP tool result.

Read-only: it never writes a note. It does build/refresh the derived index, which
is disposable by design.
"""

from __future__ import annotations

import asyncio
import json
import time
from dataclasses import dataclass, field
from pathlib import Path
Expand Down Expand Up @@ -105,6 +107,7 @@
def run(omi_dir: Path | str, *, queries: tuple[str, ...] = SAMPLE_QUERIES) -> Report:
"""Measure retrieval on ``omi_dir`` and return the report."""
from omind import ai_usage, embed, hooks, recall, searchindex
from omind.server import build_server
from omind.store import OmiStore

omi = Path(omi_dir).expanduser()
Expand All @@ -119,6 +122,14 @@
"count",
str(embed.status()["reason"] or "on"),
)
tools = asyncio.run(build_server(omi).list_tools())
tool_schemas = json.dumps(
[tool.model_dump(mode="json", exclude_none=True) for tool in tools],
ensure_ascii=False,
separators=(",", ":"),
)
report.add("MCP tools exposed", len(tools), "count")
report.add("MCP tool schemas", ai_usage.estimate_tokens(tool_schemas), "tokens")

if not searchindex.available():
report.add("search index", 0, "count", "unavailable — search scans the vault")
Expand Down Expand Up @@ -218,8 +229,6 @@
def _listing_tokens(notes: list[Any]) -> tuple[int, int]:
"""``(paged, unpaged)`` token estimate for the listing payload — the tool
result that was ~87k tokens on a 744-note vault before it was paged."""
import json

from omind import ai_usage
from omind.server import DEFAULT_PAGE

Expand Down
45 changes: 1 addition & 44 deletions src/omind/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ async def stdout_writer() -> None:
#: Default and hard-cap page sizes for every list-shaped tool. Unbounded list
#: tools were the single largest token leak in the memory layer: `list-notes` on
#: a 744-note vault returned ~348 KB (~87k tokens) in ONE tool result, and
#: graph-orphans/graph-dangling returned hundreds of rows nobody paged through.
#: graph(op=orphans|dangling) returned hundreds of rows nobody paged through.
#: An agent that needs more asks for the next page; an agent that needed one
#: note no longer pays for the whole vault.
DEFAULT_PAGE = 25
Expand Down Expand Up @@ -468,49 +468,6 @@ def graph_tool(
) -> dict[str, object]:
return _graph_query(op, source, target, limit, offset)

@mcp.tool(
name="graph-path",
description=(
"Shortest [[wikilink]] path between two notes, as a list of filenames; "
"`path` is null when no path connects them. Deprecated: use graph "
"with op=path; this compatibility name will be removed next release."
),
)
def graph_path(source: str, target: str) -> dict[str, object]:
return _graph_query("path", source, target)

@mcp.tool(
name="graph-orphans",
description=(
"One page of notes with no inbound or outbound [[wikilinks]]. "
"Deprecated: use graph with op=orphans; this compatibility name "
"will be removed next release."
),
)
def graph_orphans(limit: int = DEFAULT_PAGE, offset: int = 0) -> dict[str, object]:
return _graph_query("orphans", limit=limit, offset=offset)

@mcp.tool(
name="graph-dangling",
description=(
"One page of [[wikilinks]] resolving to no existing note, with their "
"source. Deprecated: use graph with op=dangling; this compatibility "
"name will be removed next release."
),
)
def graph_dangling(limit: int = DEFAULT_PAGE, offset: int = 0) -> dict[str, object]:
return _graph_query("dangling", limit=limit, offset=offset)

@mcp.tool(
name="graph-stats",
description=(
"Whole-graph counts. Deprecated: use graph with op=stats; this "
"compatibility name will be removed next release."
),
)
def graph_stats() -> dict[str, object]:
return _graph_query("stats")

return mcp


Expand Down
9 changes: 9 additions & 0 deletions tests/test_bench.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ def test_report_covers_latency_and_tokens(tmp_path: Path) -> None:
report = bench.run(_vault(tmp_path), queries=("nebraska",))
names = [m.name for m in report.measurements]
assert "notes in vault" in names
assert "MCP tools exposed" in names
assert "MCP tool schemas" in names
assert "index build (from scratch)" in names
assert "index refresh (no changes)" in names
assert any(name.startswith("search ") for name in names)
Expand All @@ -44,6 +46,13 @@ def test_report_covers_latency_and_tokens(tmp_path: Path) -> None:
assert {"ms", "tokens", "count"} <= units


def test_report_measures_the_reduced_mcp_surface(tmp_path: Path) -> None:
report = bench.run(_vault(tmp_path), queries=("nebraska",))
by_name = {m.name: m.value for m in report.measurements}
assert by_name["MCP tools exposed"] == 13
assert by_name["MCP tool schemas"] > 0
Comment on lines +49 to +53

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Protect the schema-token metric with a budget.

The assertion MCP tool schemas > 0 only proves that serialization returned data. It does not detect schema growth or shrinkage. Assert the recorded 2,114-token baseline, or use a documented tolerance or upper bound when the MCP dependency can vary.

🤖 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 `@tests/test_bench.py` around lines 49 - 53, Update
test_report_measures_the_reduced_mcp_surface so the “MCP tool schemas”
measurement is checked against the documented 2,114-token baseline, using an
explicitly documented tolerance or upper bound if dependency variation requires
it, instead of only asserting it is positive.



def test_paging_is_reported_as_a_token_saving(tmp_path: Path) -> None:
report = bench.run(_vault(tmp_path), queries=("nebraska",))
by_name = {m.name: m.value for m in report.measurements}
Expand Down
29 changes: 7 additions & 22 deletions tests/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,6 @@
"list-tags",
"graph-neighbors",
"graph",
"graph-path",
"graph-orphans",
"graph-dangling",
"graph-stats",
}


Expand Down Expand Up @@ -190,8 +186,6 @@ def test_every_list_tool_is_bounded(server: FastMCP) -> None:
for tool, args in (
("backlinks", {"name": "Note 00.md", "limit": 1}),
("list-tags", {"limit": 1}),
("graph-orphans", {"limit": 1}),
("graph-dangling", {"limit": 1}),
("graph-neighbors", {"name": "Linker", "limit": 1}),
("graph", {"op": "orphans", "limit": 1}),
("graph", {"op": "dangling", "limit": 1}),
Expand Down Expand Up @@ -276,24 +270,15 @@ def test_graph_tools(server: FastMCP) -> None:
nbrs = call(server, "graph-neighbors", {"name": "A", "depth": 2, "direction": "out"})["result"]
assert [n["filename"] for n in nbrs] == ["B.md", "C.md"]

assert call(server, "graph-path", {"source": "A", "target": "C"})["path"] == [
"A.md",
"B.md",
"C.md",
]
assert call(server, "graph-orphans", {})["result"] == ["Lonely.md"]
dangling = call(server, "graph-dangling", {})["result"]
assert dangling == [{"source": "Lonely.md", "target": "Ghost"}]
assert call(server, "graph-stats", {})["notes"] == 4

# New unified surface; legacy names above remain for one compatibility release.
assert call(
server,
"graph",
{"op": "path", "source": "A", "target": "C"},
)["path"] == ["A.md", "B.md", "C.md"]
assert call(server, "graph", {"op": "orphans"})["result"] == ["Lonely.md"]
assert call(server, "graph", {"op": "dangling"})["result"] == dangling
assert call(server, "graph", {"op": "dangling"})["result"] == [
{"source": "Lonely.md", "target": "Ghost"}
]
assert call(server, "graph", {"op": "stats"})["notes"] == 4


Expand All @@ -318,12 +303,12 @@ def counting(omi: Path) -> Any:
monkeypatch.setattr(graph_mod, "build_graph", counting)
server = build_server(omi_dir, node_id="testnode-abc123")
call(server, "create-note", {"title": "A", "connections": ["B"]})
call(server, "graph-stats", {})
call(server, "graph-orphans", {})
call(server, "graph-dangling", {})
call(server, "graph", {"op": "stats"})
call(server, "graph", {"op": "orphans"})
call(server, "graph", {"op": "dangling"})
assert calls["n"] == 1 # three graph queries, one build (cached)
call(server, "create-note", {"title": "B"}) # a write changes the vault
call(server, "graph-stats", {})
call(server, "graph", {"op": "stats"})
assert calls["n"] == 2 # cache busted, rebuilt once


Expand Down