| 2026-10-12 |

memory: a fact written under a new spelling of its key merges into the one that survives
...
The extractor invents a key every time it meets the same fact — `language`,
`preferred_language`, `ui_language` — and the upsert keys on the exact string, so
the base grew several spellings of each durable fact. On prod, 24 facts across 11
clusters share a key once noise prefixes are dropped, 10 of them under the exact
same value.
upsert_fact(dedupe=True) now compares the *tokens* of the normalized key — set
equality, not overlap, and never the value: `ui_language` and `language` are one
key while `dark` and `dark_ui` are two. The row already holding the writer's exact
key wins; otherwise the incoming one does. Losers are marked, not deleted: each
keeps its key and its value and gains `merged_into`, so the merge is reversible
with one UPDATE. Off by default, so the memory tool still writes exactly the row
it asked for.
Every read path filters `merged_into IS NULL`, and the mark is reachable only
where it is meant to be — get_all_facts/fact_count gained include_merged, and
GET /admin/memory?include_merged=true exposes it. search_facts deliberately has
no such flag: the injection path must not be able to see a hidden row. Forgetting
a key drops its hidden aliases first, so none is left pointing at nothing.
Three fixes that fell out of the same read:
- expires_at was overwritten with EXCLUDED.expires_at on every upsert, and the
extractor always passes None — so it silently cleared any expiry the agent had
set. Both DO UPDATE clauses now COALESCE.
- The ILIKE fallback was the one read path that served expired facts.
- The `list` action promised "all stored facts" and returned categories. The code
was right and the description was wrong; the description now says so.
The extraction prompt stopped allowing infrastructure — servers, hosts, addresses,
running services — while the agent and the planner already forbade it; that
loophole is where 423 technical facts came from. It also forbids secrets outright,
and asks for stable snake_case keys. Expiry is now documented as being for facts
with a known end, not for facts you merely doubt.
Facts reach the prompt with their age ("updated 34d ago") alongside confidence:
nothing expires on a clock, so the injection says how old a fact is and lets the
agent weigh it.
Eugene Sukhodolskiy
committed
6 hours ago
|
| 2026-05-23 |
Pass explicit ToolContext to tools instead of hidden ContextVars
...
Add ToolContext dataclass (session_id, event_sink, stop_event, model,
user_id, user_role, user_info) and thread it through the execution chain:
Agent._execute_tools_with_sink → ToolExecutor → tool.execute().
All ~25 tools updated to accept ctx parameter. Tools that previously
read ContextVar now prefer ctx when provided, falling back to
ContextVar for backward compatibility.
Tests updated to pass ToolContext explicitly — no more test fixtures
that set current_session_id / current_user_id ContextVars.
ContextVar setters remain as fallback for non-tool consumers
(ai_helper, context_builder, planning) and will be removed in a
follow-up refactor.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 23 May
|
| 2026-05-16 |
Enhance native toolset and add persistent KV store
...
- Add PostgreSQL-backed KvStore (navi/store/) for session-scoped data.
- Migrate todo and scratchpad from in-memory dicts to KvStore.
- Filesystem: add copy, grep, diff actions; compress description.
- CodeExec: remove language param, expose working_dir in schema.
- ImageView: resize to 1024px JPEG + Content-Type guard for URLs.
- Memory list: return distinct categories instead of all facts.
- SSH: add scp action with upload/download support.
- Update CLAUDE.md (Postgres-only), docs/tools.md, add docs/store.md.
- Fix agent/planning/context_builder async signatures for todo helpers.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 16 May
|
| 2026-05-15 |
Refactor: move tool helper modules into _internal subpackage
...
Moves non-tool infrastructure out of navi/tools/ root so that only
actual tool classes live there:
base.py → _internal/base.py
loader.py → _internal/loader.py
middleware.py → _internal/middleware.py
logging_middleware.py → _internal/logging_middleware.py
_time_parser.py → _internal/time_parser.py
All imports updated across core/, api/, mcp/, tools/, and tests/.
No proxy files remain in navi/tools/ root.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 15 May
|
| 2026-05-10 |
Add MCP server support and fix memory tools user isolation
...
MCP integration:
- New navi/mcp/ package: client, manager, config, tools
- ToolRegistry learns register_external() for MCP tools
- reload_tools reconnects MCP servers on hot reload
- New built-in mcp_status tool
- Startup/shutdown wiring for MCP connections
- 12 new tests (unit + integration with real stdio server)
Memory tools fix:
- memory, memory_save, memory_search, memory_forget now read
current_user_id from tool context and pass it to MemoryStore
- Fixes invisible facts for authenticated users
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 10 May
|
| 2026-04-28 |
Wire pgvector semantic search into memory system
...
- Add vector(768) column + HNSW index to memory_facts
- Add LLMBackend.embed() with Ollama + fallback implementation
- MemoryStore: cosine-distance search with ILIKE fallback
- New memory tool params: source, confidence, expires_days, source_context
- Update extractor, sqlite_store, deps wiring
- Add pgvector to requirements
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 28 Apr
|
| 2026-04-14 |
Consolidate memory_search/save/forget into single memory tool
...
Three separate tools → one tool with action enum (save/search/forget/list).
Reduces tool-slot pressure; same functionality, same MemoryStore backend.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 14 Apr
|