gobby 0.2.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gobby/__init__.py +3 -0
- gobby/adapters/__init__.py +30 -0
- gobby/adapters/base.py +93 -0
- gobby/adapters/claude_code.py +276 -0
- gobby/adapters/codex.py +1292 -0
- gobby/adapters/gemini.py +343 -0
- gobby/agents/__init__.py +37 -0
- gobby/agents/codex_session.py +120 -0
- gobby/agents/constants.py +112 -0
- gobby/agents/context.py +362 -0
- gobby/agents/definitions.py +133 -0
- gobby/agents/gemini_session.py +111 -0
- gobby/agents/registry.py +618 -0
- gobby/agents/runner.py +968 -0
- gobby/agents/session.py +259 -0
- gobby/agents/spawn.py +916 -0
- gobby/agents/spawners/__init__.py +77 -0
- gobby/agents/spawners/base.py +142 -0
- gobby/agents/spawners/cross_platform.py +266 -0
- gobby/agents/spawners/embedded.py +225 -0
- gobby/agents/spawners/headless.py +226 -0
- gobby/agents/spawners/linux.py +125 -0
- gobby/agents/spawners/macos.py +277 -0
- gobby/agents/spawners/windows.py +308 -0
- gobby/agents/tty_config.py +319 -0
- gobby/autonomous/__init__.py +32 -0
- gobby/autonomous/progress_tracker.py +447 -0
- gobby/autonomous/stop_registry.py +269 -0
- gobby/autonomous/stuck_detector.py +383 -0
- gobby/cli/__init__.py +67 -0
- gobby/cli/__main__.py +8 -0
- gobby/cli/agents.py +529 -0
- gobby/cli/artifacts.py +266 -0
- gobby/cli/daemon.py +329 -0
- gobby/cli/extensions.py +526 -0
- gobby/cli/github.py +263 -0
- gobby/cli/init.py +53 -0
- gobby/cli/install.py +614 -0
- gobby/cli/installers/__init__.py +37 -0
- gobby/cli/installers/antigravity.py +65 -0
- gobby/cli/installers/claude.py +363 -0
- gobby/cli/installers/codex.py +192 -0
- gobby/cli/installers/gemini.py +294 -0
- gobby/cli/installers/git_hooks.py +377 -0
- gobby/cli/installers/shared.py +737 -0
- gobby/cli/linear.py +250 -0
- gobby/cli/mcp.py +30 -0
- gobby/cli/mcp_proxy.py +698 -0
- gobby/cli/memory.py +304 -0
- gobby/cli/merge.py +384 -0
- gobby/cli/projects.py +79 -0
- gobby/cli/sessions.py +622 -0
- gobby/cli/tasks/__init__.py +30 -0
- gobby/cli/tasks/_utils.py +658 -0
- gobby/cli/tasks/ai.py +1025 -0
- gobby/cli/tasks/commits.py +169 -0
- gobby/cli/tasks/crud.py +685 -0
- gobby/cli/tasks/deps.py +135 -0
- gobby/cli/tasks/labels.py +63 -0
- gobby/cli/tasks/main.py +273 -0
- gobby/cli/tasks/search.py +178 -0
- gobby/cli/tui.py +34 -0
- gobby/cli/utils.py +513 -0
- gobby/cli/workflows.py +927 -0
- gobby/cli/worktrees.py +481 -0
- gobby/config/__init__.py +129 -0
- gobby/config/app.py +551 -0
- gobby/config/extensions.py +167 -0
- gobby/config/features.py +472 -0
- gobby/config/llm_providers.py +98 -0
- gobby/config/logging.py +66 -0
- gobby/config/mcp.py +346 -0
- gobby/config/persistence.py +247 -0
- gobby/config/servers.py +141 -0
- gobby/config/sessions.py +250 -0
- gobby/config/tasks.py +784 -0
- gobby/hooks/__init__.py +104 -0
- gobby/hooks/artifact_capture.py +213 -0
- gobby/hooks/broadcaster.py +243 -0
- gobby/hooks/event_handlers.py +723 -0
- gobby/hooks/events.py +218 -0
- gobby/hooks/git.py +169 -0
- gobby/hooks/health_monitor.py +171 -0
- gobby/hooks/hook_manager.py +856 -0
- gobby/hooks/hook_types.py +575 -0
- gobby/hooks/plugins.py +813 -0
- gobby/hooks/session_coordinator.py +396 -0
- gobby/hooks/verification_runner.py +268 -0
- gobby/hooks/webhooks.py +339 -0
- gobby/install/claude/commands/gobby/bug.md +51 -0
- gobby/install/claude/commands/gobby/chore.md +51 -0
- gobby/install/claude/commands/gobby/epic.md +52 -0
- gobby/install/claude/commands/gobby/eval.md +235 -0
- gobby/install/claude/commands/gobby/feat.md +49 -0
- gobby/install/claude/commands/gobby/nit.md +52 -0
- gobby/install/claude/commands/gobby/ref.md +52 -0
- gobby/install/claude/hooks/HOOK_SCHEMAS.md +632 -0
- gobby/install/claude/hooks/hook_dispatcher.py +364 -0
- gobby/install/claude/hooks/validate_settings.py +102 -0
- gobby/install/claude/hooks-template.json +118 -0
- gobby/install/codex/hooks/hook_dispatcher.py +153 -0
- gobby/install/codex/prompts/forget.md +7 -0
- gobby/install/codex/prompts/memories.md +7 -0
- gobby/install/codex/prompts/recall.md +7 -0
- gobby/install/codex/prompts/remember.md +13 -0
- gobby/install/gemini/hooks/hook_dispatcher.py +268 -0
- gobby/install/gemini/hooks-template.json +138 -0
- gobby/install/shared/plugins/code_guardian.py +456 -0
- gobby/install/shared/plugins/example_notify.py +331 -0
- gobby/integrations/__init__.py +10 -0
- gobby/integrations/github.py +145 -0
- gobby/integrations/linear.py +145 -0
- gobby/llm/__init__.py +40 -0
- gobby/llm/base.py +120 -0
- gobby/llm/claude.py +578 -0
- gobby/llm/claude_executor.py +503 -0
- gobby/llm/codex.py +322 -0
- gobby/llm/codex_executor.py +513 -0
- gobby/llm/executor.py +316 -0
- gobby/llm/factory.py +34 -0
- gobby/llm/gemini.py +258 -0
- gobby/llm/gemini_executor.py +339 -0
- gobby/llm/litellm.py +287 -0
- gobby/llm/litellm_executor.py +303 -0
- gobby/llm/resolver.py +499 -0
- gobby/llm/service.py +236 -0
- gobby/mcp_proxy/__init__.py +29 -0
- gobby/mcp_proxy/actions.py +175 -0
- gobby/mcp_proxy/daemon_control.py +198 -0
- gobby/mcp_proxy/importer.py +436 -0
- gobby/mcp_proxy/lazy.py +325 -0
- gobby/mcp_proxy/manager.py +798 -0
- gobby/mcp_proxy/metrics.py +609 -0
- gobby/mcp_proxy/models.py +139 -0
- gobby/mcp_proxy/registries.py +215 -0
- gobby/mcp_proxy/schema_hash.py +381 -0
- gobby/mcp_proxy/semantic_search.py +706 -0
- gobby/mcp_proxy/server.py +549 -0
- gobby/mcp_proxy/services/__init__.py +0 -0
- gobby/mcp_proxy/services/fallback.py +306 -0
- gobby/mcp_proxy/services/recommendation.py +224 -0
- gobby/mcp_proxy/services/server_mgmt.py +214 -0
- gobby/mcp_proxy/services/system.py +72 -0
- gobby/mcp_proxy/services/tool_filter.py +231 -0
- gobby/mcp_proxy/services/tool_proxy.py +309 -0
- gobby/mcp_proxy/stdio.py +565 -0
- gobby/mcp_proxy/tools/__init__.py +27 -0
- gobby/mcp_proxy/tools/agents.py +1103 -0
- gobby/mcp_proxy/tools/artifacts.py +207 -0
- gobby/mcp_proxy/tools/hub.py +335 -0
- gobby/mcp_proxy/tools/internal.py +337 -0
- gobby/mcp_proxy/tools/memory.py +543 -0
- gobby/mcp_proxy/tools/merge.py +422 -0
- gobby/mcp_proxy/tools/metrics.py +283 -0
- gobby/mcp_proxy/tools/orchestration/__init__.py +23 -0
- gobby/mcp_proxy/tools/orchestration/cleanup.py +619 -0
- gobby/mcp_proxy/tools/orchestration/monitor.py +380 -0
- gobby/mcp_proxy/tools/orchestration/orchestrate.py +746 -0
- gobby/mcp_proxy/tools/orchestration/review.py +736 -0
- gobby/mcp_proxy/tools/orchestration/utils.py +16 -0
- gobby/mcp_proxy/tools/session_messages.py +1056 -0
- gobby/mcp_proxy/tools/task_dependencies.py +219 -0
- gobby/mcp_proxy/tools/task_expansion.py +591 -0
- gobby/mcp_proxy/tools/task_github.py +393 -0
- gobby/mcp_proxy/tools/task_linear.py +379 -0
- gobby/mcp_proxy/tools/task_orchestration.py +77 -0
- gobby/mcp_proxy/tools/task_readiness.py +522 -0
- gobby/mcp_proxy/tools/task_sync.py +351 -0
- gobby/mcp_proxy/tools/task_validation.py +843 -0
- gobby/mcp_proxy/tools/tasks/__init__.py +25 -0
- gobby/mcp_proxy/tools/tasks/_context.py +112 -0
- gobby/mcp_proxy/tools/tasks/_crud.py +516 -0
- gobby/mcp_proxy/tools/tasks/_factory.py +176 -0
- gobby/mcp_proxy/tools/tasks/_helpers.py +129 -0
- gobby/mcp_proxy/tools/tasks/_lifecycle.py +517 -0
- gobby/mcp_proxy/tools/tasks/_lifecycle_validation.py +301 -0
- gobby/mcp_proxy/tools/tasks/_resolution.py +55 -0
- gobby/mcp_proxy/tools/tasks/_search.py +215 -0
- gobby/mcp_proxy/tools/tasks/_session.py +125 -0
- gobby/mcp_proxy/tools/workflows.py +973 -0
- gobby/mcp_proxy/tools/worktrees.py +1264 -0
- gobby/mcp_proxy/transports/__init__.py +0 -0
- gobby/mcp_proxy/transports/base.py +95 -0
- gobby/mcp_proxy/transports/factory.py +44 -0
- gobby/mcp_proxy/transports/http.py +139 -0
- gobby/mcp_proxy/transports/stdio.py +213 -0
- gobby/mcp_proxy/transports/websocket.py +136 -0
- gobby/memory/backends/__init__.py +116 -0
- gobby/memory/backends/mem0.py +408 -0
- gobby/memory/backends/memu.py +485 -0
- gobby/memory/backends/null.py +111 -0
- gobby/memory/backends/openmemory.py +537 -0
- gobby/memory/backends/sqlite.py +304 -0
- gobby/memory/context.py +87 -0
- gobby/memory/manager.py +1001 -0
- gobby/memory/protocol.py +451 -0
- gobby/memory/search/__init__.py +66 -0
- gobby/memory/search/text.py +127 -0
- gobby/memory/viz.py +258 -0
- gobby/prompts/__init__.py +13 -0
- gobby/prompts/defaults/expansion/system.md +119 -0
- gobby/prompts/defaults/expansion/user.md +48 -0
- gobby/prompts/defaults/external_validation/agent.md +72 -0
- gobby/prompts/defaults/external_validation/external.md +63 -0
- gobby/prompts/defaults/external_validation/spawn.md +83 -0
- gobby/prompts/defaults/external_validation/system.md +6 -0
- gobby/prompts/defaults/features/import_mcp.md +22 -0
- gobby/prompts/defaults/features/import_mcp_github.md +17 -0
- gobby/prompts/defaults/features/import_mcp_search.md +16 -0
- gobby/prompts/defaults/features/recommend_tools.md +32 -0
- gobby/prompts/defaults/features/recommend_tools_hybrid.md +35 -0
- gobby/prompts/defaults/features/recommend_tools_llm.md +30 -0
- gobby/prompts/defaults/features/server_description.md +20 -0
- gobby/prompts/defaults/features/server_description_system.md +6 -0
- gobby/prompts/defaults/features/task_description.md +31 -0
- gobby/prompts/defaults/features/task_description_system.md +6 -0
- gobby/prompts/defaults/features/tool_summary.md +17 -0
- gobby/prompts/defaults/features/tool_summary_system.md +6 -0
- gobby/prompts/defaults/research/step.md +58 -0
- gobby/prompts/defaults/validation/criteria.md +47 -0
- gobby/prompts/defaults/validation/validate.md +38 -0
- gobby/prompts/loader.py +346 -0
- gobby/prompts/models.py +113 -0
- gobby/py.typed +0 -0
- gobby/runner.py +488 -0
- gobby/search/__init__.py +23 -0
- gobby/search/protocol.py +104 -0
- gobby/search/tfidf.py +232 -0
- gobby/servers/__init__.py +7 -0
- gobby/servers/http.py +636 -0
- gobby/servers/models.py +31 -0
- gobby/servers/routes/__init__.py +23 -0
- gobby/servers/routes/admin.py +416 -0
- gobby/servers/routes/dependencies.py +118 -0
- gobby/servers/routes/mcp/__init__.py +24 -0
- gobby/servers/routes/mcp/hooks.py +135 -0
- gobby/servers/routes/mcp/plugins.py +121 -0
- gobby/servers/routes/mcp/tools.py +1337 -0
- gobby/servers/routes/mcp/webhooks.py +159 -0
- gobby/servers/routes/sessions.py +582 -0
- gobby/servers/websocket.py +766 -0
- gobby/sessions/__init__.py +13 -0
- gobby/sessions/analyzer.py +322 -0
- gobby/sessions/lifecycle.py +240 -0
- gobby/sessions/manager.py +563 -0
- gobby/sessions/processor.py +225 -0
- gobby/sessions/summary.py +532 -0
- gobby/sessions/transcripts/__init__.py +41 -0
- gobby/sessions/transcripts/base.py +125 -0
- gobby/sessions/transcripts/claude.py +386 -0
- gobby/sessions/transcripts/codex.py +143 -0
- gobby/sessions/transcripts/gemini.py +195 -0
- gobby/storage/__init__.py +21 -0
- gobby/storage/agents.py +409 -0
- gobby/storage/artifact_classifier.py +341 -0
- gobby/storage/artifacts.py +285 -0
- gobby/storage/compaction.py +67 -0
- gobby/storage/database.py +357 -0
- gobby/storage/inter_session_messages.py +194 -0
- gobby/storage/mcp.py +680 -0
- gobby/storage/memories.py +562 -0
- gobby/storage/merge_resolutions.py +550 -0
- gobby/storage/migrations.py +860 -0
- gobby/storage/migrations_legacy.py +1359 -0
- gobby/storage/projects.py +166 -0
- gobby/storage/session_messages.py +251 -0
- gobby/storage/session_tasks.py +97 -0
- gobby/storage/sessions.py +817 -0
- gobby/storage/task_dependencies.py +223 -0
- gobby/storage/tasks/__init__.py +42 -0
- gobby/storage/tasks/_aggregates.py +180 -0
- gobby/storage/tasks/_crud.py +449 -0
- gobby/storage/tasks/_id.py +104 -0
- gobby/storage/tasks/_lifecycle.py +311 -0
- gobby/storage/tasks/_manager.py +889 -0
- gobby/storage/tasks/_models.py +300 -0
- gobby/storage/tasks/_ordering.py +119 -0
- gobby/storage/tasks/_path_cache.py +110 -0
- gobby/storage/tasks/_queries.py +343 -0
- gobby/storage/tasks/_search.py +143 -0
- gobby/storage/workflow_audit.py +393 -0
- gobby/storage/worktrees.py +547 -0
- gobby/sync/__init__.py +29 -0
- gobby/sync/github.py +333 -0
- gobby/sync/linear.py +304 -0
- gobby/sync/memories.py +284 -0
- gobby/sync/tasks.py +641 -0
- gobby/tasks/__init__.py +8 -0
- gobby/tasks/build_verification.py +193 -0
- gobby/tasks/commits.py +633 -0
- gobby/tasks/context.py +747 -0
- gobby/tasks/criteria.py +342 -0
- gobby/tasks/enhanced_validator.py +226 -0
- gobby/tasks/escalation.py +263 -0
- gobby/tasks/expansion.py +626 -0
- gobby/tasks/external_validator.py +764 -0
- gobby/tasks/issue_extraction.py +171 -0
- gobby/tasks/prompts/expand.py +327 -0
- gobby/tasks/research.py +421 -0
- gobby/tasks/tdd.py +352 -0
- gobby/tasks/tree_builder.py +263 -0
- gobby/tasks/validation.py +712 -0
- gobby/tasks/validation_history.py +357 -0
- gobby/tasks/validation_models.py +89 -0
- gobby/tools/__init__.py +0 -0
- gobby/tools/summarizer.py +170 -0
- gobby/tui/__init__.py +5 -0
- gobby/tui/api_client.py +281 -0
- gobby/tui/app.py +327 -0
- gobby/tui/screens/__init__.py +25 -0
- gobby/tui/screens/agents.py +333 -0
- gobby/tui/screens/chat.py +450 -0
- gobby/tui/screens/dashboard.py +377 -0
- gobby/tui/screens/memory.py +305 -0
- gobby/tui/screens/metrics.py +231 -0
- gobby/tui/screens/orchestrator.py +904 -0
- gobby/tui/screens/sessions.py +412 -0
- gobby/tui/screens/tasks.py +442 -0
- gobby/tui/screens/workflows.py +289 -0
- gobby/tui/screens/worktrees.py +174 -0
- gobby/tui/widgets/__init__.py +21 -0
- gobby/tui/widgets/chat.py +210 -0
- gobby/tui/widgets/conductor.py +104 -0
- gobby/tui/widgets/menu.py +132 -0
- gobby/tui/widgets/message_panel.py +160 -0
- gobby/tui/widgets/review_gate.py +224 -0
- gobby/tui/widgets/task_tree.py +99 -0
- gobby/tui/widgets/token_budget.py +166 -0
- gobby/tui/ws_client.py +258 -0
- gobby/utils/__init__.py +3 -0
- gobby/utils/daemon_client.py +235 -0
- gobby/utils/git.py +222 -0
- gobby/utils/id.py +38 -0
- gobby/utils/json_helpers.py +161 -0
- gobby/utils/logging.py +376 -0
- gobby/utils/machine_id.py +135 -0
- gobby/utils/metrics.py +589 -0
- gobby/utils/project_context.py +182 -0
- gobby/utils/project_init.py +263 -0
- gobby/utils/status.py +256 -0
- gobby/utils/validation.py +80 -0
- gobby/utils/version.py +23 -0
- gobby/workflows/__init__.py +4 -0
- gobby/workflows/actions.py +1310 -0
- gobby/workflows/approval_flow.py +138 -0
- gobby/workflows/artifact_actions.py +103 -0
- gobby/workflows/audit_helpers.py +110 -0
- gobby/workflows/autonomous_actions.py +286 -0
- gobby/workflows/context_actions.py +394 -0
- gobby/workflows/definitions.py +130 -0
- gobby/workflows/detection_helpers.py +208 -0
- gobby/workflows/engine.py +485 -0
- gobby/workflows/evaluator.py +669 -0
- gobby/workflows/git_utils.py +96 -0
- gobby/workflows/hooks.py +169 -0
- gobby/workflows/lifecycle_evaluator.py +613 -0
- gobby/workflows/llm_actions.py +70 -0
- gobby/workflows/loader.py +333 -0
- gobby/workflows/mcp_actions.py +60 -0
- gobby/workflows/memory_actions.py +272 -0
- gobby/workflows/premature_stop.py +164 -0
- gobby/workflows/session_actions.py +139 -0
- gobby/workflows/state_actions.py +123 -0
- gobby/workflows/state_manager.py +104 -0
- gobby/workflows/stop_signal_actions.py +163 -0
- gobby/workflows/summary_actions.py +344 -0
- gobby/workflows/task_actions.py +249 -0
- gobby/workflows/task_enforcement_actions.py +901 -0
- gobby/workflows/templates.py +52 -0
- gobby/workflows/todo_actions.py +84 -0
- gobby/workflows/webhook.py +223 -0
- gobby/workflows/webhook_executor.py +399 -0
- gobby/worktrees/__init__.py +5 -0
- gobby/worktrees/git.py +690 -0
- gobby/worktrees/merge/__init__.py +20 -0
- gobby/worktrees/merge/conflict_parser.py +177 -0
- gobby/worktrees/merge/resolver.py +485 -0
- gobby-0.2.5.dist-info/METADATA +351 -0
- gobby-0.2.5.dist-info/RECORD +383 -0
- gobby-0.2.5.dist-info/WHEEL +5 -0
- gobby-0.2.5.dist-info/entry_points.txt +2 -0
- gobby-0.2.5.dist-info/licenses/LICENSE.md +193 -0
- gobby-0.2.5.dist-info/top_level.txt +1 -0
gobby/memory/protocol.py
ADDED
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
"""Memory backend protocol types.
|
|
2
|
+
|
|
3
|
+
This module defines the abstraction layer that enables pluggable memory backends.
|
|
4
|
+
Users can choose between Gobby's built-in SQLite backend or plug in external
|
|
5
|
+
memory systems like Mem0, OpenMemory, or MemU.
|
|
6
|
+
|
|
7
|
+
Types:
|
|
8
|
+
- MemoryCapability: Enum of capabilities a backend can support
|
|
9
|
+
- MemoryQuery: Dataclass for search parameters
|
|
10
|
+
- MediaAttachment: Dataclass for multimodal memory support
|
|
11
|
+
- MemoryRecord: Backend-agnostic memory representation
|
|
12
|
+
- MemoryBackendProtocol: Protocol interface that backends must implement
|
|
13
|
+
|
|
14
|
+
Example:
|
|
15
|
+
from gobby.memory.protocol import MemoryBackendProtocol, MemoryCapability
|
|
16
|
+
|
|
17
|
+
class MyBackend:
|
|
18
|
+
def capabilities(self) -> set[MemoryCapability]:
|
|
19
|
+
return {MemoryCapability.CREATE, MemoryCapability.READ}
|
|
20
|
+
# ... implement other required methods
|
|
21
|
+
|
|
22
|
+
assert isinstance(MyBackend(), MemoryBackendProtocol)
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
from dataclasses import dataclass, field
|
|
28
|
+
from datetime import UTC, datetime
|
|
29
|
+
from enum import Enum
|
|
30
|
+
from typing import Any, Protocol, runtime_checkable
|
|
31
|
+
|
|
32
|
+
__all__ = [
|
|
33
|
+
"MemoryCapability",
|
|
34
|
+
"MemoryQuery",
|
|
35
|
+
"MediaAttachment",
|
|
36
|
+
"MemoryRecord",
|
|
37
|
+
"MemoryBackendProtocol",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class MemoryCapability(Enum):
|
|
42
|
+
"""Capabilities that a memory backend can support.
|
|
43
|
+
|
|
44
|
+
Backends declare which capabilities they support via the capabilities()
|
|
45
|
+
method. The MemoryManager uses these to gracefully degrade when a backend
|
|
46
|
+
doesn't support a requested operation.
|
|
47
|
+
|
|
48
|
+
Basic CRUD:
|
|
49
|
+
CREATE: Store new memories
|
|
50
|
+
READ: Retrieve a specific memory by ID
|
|
51
|
+
UPDATE: Modify existing memories
|
|
52
|
+
DELETE: Remove memories
|
|
53
|
+
|
|
54
|
+
Search capabilities:
|
|
55
|
+
SEARCH_TEXT: Text-based substring/keyword search
|
|
56
|
+
SEARCH_SEMANTIC: Embedding-based semantic similarity search
|
|
57
|
+
SEARCH_HYBRID: Combined text + semantic search
|
|
58
|
+
|
|
59
|
+
Advanced features:
|
|
60
|
+
TAGS: Tag-based filtering and organization
|
|
61
|
+
IMPORTANCE: Importance scoring and filtering
|
|
62
|
+
CROSSREF: Cross-referencing between related memories
|
|
63
|
+
MEDIA: Multimodal memory support (images, etc.)
|
|
64
|
+
DECAY: Time-based importance decay
|
|
65
|
+
|
|
66
|
+
MCP-aligned operations (aliases for compatibility):
|
|
67
|
+
REMEMBER: Alias for CREATE
|
|
68
|
+
RECALL: Alias for READ + SEARCH
|
|
69
|
+
FORGET: Alias for DELETE
|
|
70
|
+
SEARCH: Generic search (text or semantic)
|
|
71
|
+
LIST: List/enumerate memories
|
|
72
|
+
EXISTS: Check if memory exists
|
|
73
|
+
STATS: Get statistics about memories
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
# Basic CRUD
|
|
77
|
+
CREATE = "create"
|
|
78
|
+
READ = "read"
|
|
79
|
+
UPDATE = "update"
|
|
80
|
+
DELETE = "delete"
|
|
81
|
+
|
|
82
|
+
# Search capabilities
|
|
83
|
+
SEARCH_TEXT = "search_text"
|
|
84
|
+
SEARCH_SEMANTIC = "search_semantic"
|
|
85
|
+
SEARCH_HYBRID = "search_hybrid"
|
|
86
|
+
|
|
87
|
+
# Advanced features
|
|
88
|
+
TAGS = "tags"
|
|
89
|
+
IMPORTANCE = "importance"
|
|
90
|
+
CROSSREF = "crossref"
|
|
91
|
+
MEDIA = "media"
|
|
92
|
+
DECAY = "decay"
|
|
93
|
+
|
|
94
|
+
# MCP-aligned operations (aliases)
|
|
95
|
+
REMEMBER = "remember"
|
|
96
|
+
RECALL = "recall"
|
|
97
|
+
FORGET = "forget"
|
|
98
|
+
SEARCH = "search"
|
|
99
|
+
LIST = "list"
|
|
100
|
+
EXISTS = "exists"
|
|
101
|
+
STATS = "stats"
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@dataclass(frozen=True)
|
|
105
|
+
class MemoryQuery:
|
|
106
|
+
"""Search parameters for memory recall operations.
|
|
107
|
+
|
|
108
|
+
Attributes:
|
|
109
|
+
text: Search query text (required for search operations)
|
|
110
|
+
project_id: Filter by project ID
|
|
111
|
+
user_id: Filter by user ID (for multi-tenant backends)
|
|
112
|
+
limit: Maximum number of results to return
|
|
113
|
+
min_importance: Minimum importance threshold
|
|
114
|
+
memory_type: Filter by memory type (fact, preference, etc.)
|
|
115
|
+
tags_all: Memory must have ALL of these tags
|
|
116
|
+
tags_any: Memory must have at least ONE of these tags
|
|
117
|
+
tags_none: Memory must have NONE of these tags
|
|
118
|
+
search_mode: Search mode - "auto", "text", "semantic", "hybrid"
|
|
119
|
+
|
|
120
|
+
Example:
|
|
121
|
+
query = MemoryQuery(
|
|
122
|
+
text="authentication",
|
|
123
|
+
project_id="proj-123",
|
|
124
|
+
min_importance=0.5,
|
|
125
|
+
tags_all=["security"],
|
|
126
|
+
search_mode="semantic"
|
|
127
|
+
)
|
|
128
|
+
"""
|
|
129
|
+
|
|
130
|
+
text: str
|
|
131
|
+
project_id: str | None = None
|
|
132
|
+
user_id: str | None = None
|
|
133
|
+
limit: int = 10
|
|
134
|
+
min_importance: float | None = None
|
|
135
|
+
memory_type: str | None = None
|
|
136
|
+
tags_all: list[str] | None = None
|
|
137
|
+
tags_any: list[str] | None = None
|
|
138
|
+
tags_none: list[str] | None = None
|
|
139
|
+
search_mode: str = "auto"
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
@dataclass
|
|
143
|
+
class MediaAttachment:
|
|
144
|
+
"""Media attachment for multimodal memory support.
|
|
145
|
+
|
|
146
|
+
Enables memories to include images, documents, or other media files.
|
|
147
|
+
The description field can be populated by an LLM to make the media
|
|
148
|
+
searchable via text queries.
|
|
149
|
+
|
|
150
|
+
Attributes:
|
|
151
|
+
media_type: Type of media (e.g., "image", "document", "audio")
|
|
152
|
+
content_path: Path to the media file
|
|
153
|
+
mime_type: MIME type of the media (e.g., "image/png")
|
|
154
|
+
description: LLM-generated description of the media content
|
|
155
|
+
description_model: Model used to generate the description
|
|
156
|
+
metadata: Additional media-specific metadata
|
|
157
|
+
|
|
158
|
+
Example:
|
|
159
|
+
attachment = MediaAttachment(
|
|
160
|
+
media_type="image",
|
|
161
|
+
content_path="/path/to/diagram.png",
|
|
162
|
+
mime_type="image/png",
|
|
163
|
+
description="Architecture diagram showing microservices layout",
|
|
164
|
+
description_model="claude-3-haiku"
|
|
165
|
+
)
|
|
166
|
+
"""
|
|
167
|
+
|
|
168
|
+
media_type: str
|
|
169
|
+
content_path: str
|
|
170
|
+
mime_type: str
|
|
171
|
+
description: str | None = None
|
|
172
|
+
description_model: str | None = None
|
|
173
|
+
metadata: dict[str, Any] | None = None
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
@dataclass
|
|
177
|
+
class MemoryRecord:
|
|
178
|
+
"""Backend-agnostic representation of a memory.
|
|
179
|
+
|
|
180
|
+
This is the common format used across all backends. Backends convert
|
|
181
|
+
their internal representations to/from this format.
|
|
182
|
+
|
|
183
|
+
Attributes:
|
|
184
|
+
id: Unique identifier for the memory
|
|
185
|
+
content: The memory content text
|
|
186
|
+
created_at: When the memory was created
|
|
187
|
+
memory_type: Type of memory (fact, preference, pattern, context)
|
|
188
|
+
updated_at: When the memory was last updated
|
|
189
|
+
project_id: Associated project ID
|
|
190
|
+
user_id: Associated user ID (for multi-tenant backends)
|
|
191
|
+
importance: Importance score (0.0 to 1.0)
|
|
192
|
+
tags: List of tags for organization
|
|
193
|
+
source_type: Origin of memory (user, session, inferred)
|
|
194
|
+
source_session_id: Session that created the memory
|
|
195
|
+
access_count: Number of times memory was accessed
|
|
196
|
+
last_accessed_at: When memory was last accessed
|
|
197
|
+
media: List of media attachments
|
|
198
|
+
metadata: Additional backend-specific metadata
|
|
199
|
+
|
|
200
|
+
Example:
|
|
201
|
+
record = MemoryRecord(
|
|
202
|
+
id="mem-abc123",
|
|
203
|
+
content="User prefers dark mode",
|
|
204
|
+
created_at=datetime.now(UTC),
|
|
205
|
+
memory_type="preference",
|
|
206
|
+
importance=0.8,
|
|
207
|
+
tags=["ui", "settings"]
|
|
208
|
+
)
|
|
209
|
+
"""
|
|
210
|
+
|
|
211
|
+
id: str
|
|
212
|
+
content: str
|
|
213
|
+
created_at: datetime
|
|
214
|
+
memory_type: str = "fact"
|
|
215
|
+
updated_at: datetime | None = None
|
|
216
|
+
project_id: str | None = None
|
|
217
|
+
user_id: str | None = None
|
|
218
|
+
importance: float = 0.5
|
|
219
|
+
tags: list[str] = field(default_factory=list)
|
|
220
|
+
source_type: str | None = None
|
|
221
|
+
source_session_id: str | None = None
|
|
222
|
+
access_count: int = 0
|
|
223
|
+
last_accessed_at: datetime | None = None
|
|
224
|
+
media: list[MediaAttachment] = field(default_factory=list)
|
|
225
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
226
|
+
|
|
227
|
+
def to_dict(self) -> dict[str, Any]:
|
|
228
|
+
"""Convert record to dictionary for serialization."""
|
|
229
|
+
return {
|
|
230
|
+
"id": self.id,
|
|
231
|
+
"content": self.content,
|
|
232
|
+
"created_at": self.created_at.isoformat() if self.created_at else None,
|
|
233
|
+
"memory_type": self.memory_type,
|
|
234
|
+
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
|
|
235
|
+
"project_id": self.project_id,
|
|
236
|
+
"user_id": self.user_id,
|
|
237
|
+
"importance": self.importance,
|
|
238
|
+
"tags": self.tags,
|
|
239
|
+
"source_type": self.source_type,
|
|
240
|
+
"source_session_id": self.source_session_id,
|
|
241
|
+
"access_count": self.access_count,
|
|
242
|
+
"last_accessed_at": (
|
|
243
|
+
self.last_accessed_at.isoformat() if self.last_accessed_at else None
|
|
244
|
+
),
|
|
245
|
+
"media": [
|
|
246
|
+
{
|
|
247
|
+
"media_type": m.media_type,
|
|
248
|
+
"content_path": m.content_path,
|
|
249
|
+
"mime_type": m.mime_type,
|
|
250
|
+
"description": m.description,
|
|
251
|
+
"description_model": m.description_model,
|
|
252
|
+
"metadata": m.metadata,
|
|
253
|
+
}
|
|
254
|
+
for m in (self.media or [])
|
|
255
|
+
],
|
|
256
|
+
"metadata": self.metadata,
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
@classmethod
|
|
260
|
+
def from_dict(cls, data: dict[str, Any]) -> MemoryRecord:
|
|
261
|
+
"""Create record from dictionary."""
|
|
262
|
+
# Parse datetime fields
|
|
263
|
+
created_at = data.get("created_at")
|
|
264
|
+
if isinstance(created_at, str):
|
|
265
|
+
created_at = datetime.fromisoformat(created_at)
|
|
266
|
+
elif created_at is None:
|
|
267
|
+
created_at = datetime.now(UTC)
|
|
268
|
+
|
|
269
|
+
updated_at = data.get("updated_at")
|
|
270
|
+
if isinstance(updated_at, str):
|
|
271
|
+
updated_at = datetime.fromisoformat(updated_at)
|
|
272
|
+
|
|
273
|
+
last_accessed_at = data.get("last_accessed_at")
|
|
274
|
+
if isinstance(last_accessed_at, str):
|
|
275
|
+
last_accessed_at = datetime.fromisoformat(last_accessed_at)
|
|
276
|
+
|
|
277
|
+
# Parse media attachments
|
|
278
|
+
media_data = data.get("media", [])
|
|
279
|
+
media = [
|
|
280
|
+
MediaAttachment(
|
|
281
|
+
media_type=m.get("media_type", "unknown"),
|
|
282
|
+
content_path=m.get("content_path", ""),
|
|
283
|
+
mime_type=m.get("mime_type", "application/octet-stream"),
|
|
284
|
+
description=m.get("description"),
|
|
285
|
+
description_model=m.get("description_model"),
|
|
286
|
+
metadata=m.get("metadata"),
|
|
287
|
+
)
|
|
288
|
+
for m in media_data
|
|
289
|
+
]
|
|
290
|
+
|
|
291
|
+
return cls(
|
|
292
|
+
id=data["id"],
|
|
293
|
+
content=data["content"],
|
|
294
|
+
created_at=created_at,
|
|
295
|
+
memory_type=data.get("memory_type", "fact"),
|
|
296
|
+
updated_at=updated_at,
|
|
297
|
+
project_id=data.get("project_id"),
|
|
298
|
+
user_id=data.get("user_id"),
|
|
299
|
+
importance=data.get("importance", 0.5),
|
|
300
|
+
tags=data.get("tags", []),
|
|
301
|
+
source_type=data.get("source_type"),
|
|
302
|
+
source_session_id=data.get("source_session_id"),
|
|
303
|
+
access_count=data.get("access_count", 0),
|
|
304
|
+
last_accessed_at=last_accessed_at,
|
|
305
|
+
media=media,
|
|
306
|
+
metadata=data.get("metadata", {}),
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
@runtime_checkable
|
|
311
|
+
class MemoryBackendProtocol(Protocol):
|
|
312
|
+
"""Protocol interface that memory backends must implement.
|
|
313
|
+
|
|
314
|
+
Backends can implement a subset of methods based on their capabilities.
|
|
315
|
+
The capabilities() method declares which operations the backend supports,
|
|
316
|
+
allowing the MemoryManager to gracefully degrade for unsupported operations.
|
|
317
|
+
|
|
318
|
+
Required methods:
|
|
319
|
+
capabilities(): Return set of supported MemoryCapability values
|
|
320
|
+
create(): Store a new memory
|
|
321
|
+
get(): Retrieve a memory by ID
|
|
322
|
+
update(): Update an existing memory
|
|
323
|
+
delete(): Delete a memory
|
|
324
|
+
search(): Search for memories
|
|
325
|
+
list_memories(): List memories with filtering
|
|
326
|
+
|
|
327
|
+
Example:
|
|
328
|
+
class MyBackend:
|
|
329
|
+
def capabilities(self) -> set[MemoryCapability]:
|
|
330
|
+
return {MemoryCapability.CREATE, MemoryCapability.READ}
|
|
331
|
+
|
|
332
|
+
async def create(self, content: str, **kwargs) -> MemoryRecord:
|
|
333
|
+
# Implementation...
|
|
334
|
+
|
|
335
|
+
backend = MyBackend()
|
|
336
|
+
assert isinstance(backend, MemoryBackendProtocol)
|
|
337
|
+
"""
|
|
338
|
+
|
|
339
|
+
def capabilities(self) -> set[MemoryCapability]:
|
|
340
|
+
"""Return the set of capabilities this backend supports."""
|
|
341
|
+
...
|
|
342
|
+
|
|
343
|
+
async def create(
|
|
344
|
+
self,
|
|
345
|
+
content: str,
|
|
346
|
+
memory_type: str = "fact",
|
|
347
|
+
importance: float = 0.5,
|
|
348
|
+
project_id: str | None = None,
|
|
349
|
+
user_id: str | None = None,
|
|
350
|
+
tags: list[str] | None = None,
|
|
351
|
+
source_type: str | None = None,
|
|
352
|
+
source_session_id: str | None = None,
|
|
353
|
+
media: list[MediaAttachment] | None = None,
|
|
354
|
+
metadata: dict[str, Any] | None = None,
|
|
355
|
+
) -> MemoryRecord:
|
|
356
|
+
"""Create a new memory.
|
|
357
|
+
|
|
358
|
+
Args:
|
|
359
|
+
content: The memory content text
|
|
360
|
+
memory_type: Type of memory (fact, preference, etc.)
|
|
361
|
+
importance: Importance score (0.0 to 1.0)
|
|
362
|
+
project_id: Associated project ID
|
|
363
|
+
user_id: Associated user ID
|
|
364
|
+
tags: List of tags
|
|
365
|
+
source_type: Origin of memory
|
|
366
|
+
source_session_id: Session that created the memory
|
|
367
|
+
media: List of media attachments
|
|
368
|
+
metadata: Additional metadata
|
|
369
|
+
|
|
370
|
+
Returns:
|
|
371
|
+
The created MemoryRecord
|
|
372
|
+
"""
|
|
373
|
+
...
|
|
374
|
+
|
|
375
|
+
async def get(self, memory_id: str) -> MemoryRecord | None:
|
|
376
|
+
"""Retrieve a memory by ID.
|
|
377
|
+
|
|
378
|
+
Args:
|
|
379
|
+
memory_id: The memory ID to retrieve
|
|
380
|
+
|
|
381
|
+
Returns:
|
|
382
|
+
The MemoryRecord if found, None otherwise
|
|
383
|
+
"""
|
|
384
|
+
...
|
|
385
|
+
|
|
386
|
+
async def update(
|
|
387
|
+
self,
|
|
388
|
+
memory_id: str,
|
|
389
|
+
content: str | None = None,
|
|
390
|
+
importance: float | None = None,
|
|
391
|
+
tags: list[str] | None = None,
|
|
392
|
+
) -> MemoryRecord:
|
|
393
|
+
"""Update an existing memory.
|
|
394
|
+
|
|
395
|
+
Args:
|
|
396
|
+
memory_id: The memory ID to update
|
|
397
|
+
content: New content (optional)
|
|
398
|
+
importance: New importance score (optional)
|
|
399
|
+
tags: New tags (optional)
|
|
400
|
+
|
|
401
|
+
Returns:
|
|
402
|
+
The updated MemoryRecord
|
|
403
|
+
|
|
404
|
+
Raises:
|
|
405
|
+
ValueError: If memory not found
|
|
406
|
+
"""
|
|
407
|
+
...
|
|
408
|
+
|
|
409
|
+
async def delete(self, memory_id: str) -> bool:
|
|
410
|
+
"""Delete a memory.
|
|
411
|
+
|
|
412
|
+
Args:
|
|
413
|
+
memory_id: The memory ID to delete
|
|
414
|
+
|
|
415
|
+
Returns:
|
|
416
|
+
True if deleted, False if not found
|
|
417
|
+
"""
|
|
418
|
+
...
|
|
419
|
+
|
|
420
|
+
async def search(self, query: MemoryQuery) -> list[MemoryRecord]:
|
|
421
|
+
"""Search for memories.
|
|
422
|
+
|
|
423
|
+
Args:
|
|
424
|
+
query: Search parameters
|
|
425
|
+
|
|
426
|
+
Returns:
|
|
427
|
+
List of matching MemoryRecords
|
|
428
|
+
"""
|
|
429
|
+
...
|
|
430
|
+
|
|
431
|
+
async def list_memories(
|
|
432
|
+
self,
|
|
433
|
+
project_id: str | None = None,
|
|
434
|
+
user_id: str | None = None,
|
|
435
|
+
memory_type: str | None = None,
|
|
436
|
+
limit: int = 50,
|
|
437
|
+
offset: int = 0,
|
|
438
|
+
) -> list[MemoryRecord]:
|
|
439
|
+
"""List memories with optional filtering.
|
|
440
|
+
|
|
441
|
+
Args:
|
|
442
|
+
project_id: Filter by project ID
|
|
443
|
+
user_id: Filter by user ID
|
|
444
|
+
memory_type: Filter by memory type
|
|
445
|
+
limit: Maximum number of results
|
|
446
|
+
offset: Number of results to skip
|
|
447
|
+
|
|
448
|
+
Returns:
|
|
449
|
+
List of MemoryRecords
|
|
450
|
+
"""
|
|
451
|
+
...
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Memory search backend abstraction.
|
|
3
|
+
|
|
4
|
+
Provides pluggable search backends for memory recall:
|
|
5
|
+
- TF-IDF (default) - Zero-dependency local search using sklearn
|
|
6
|
+
- Text - Simple substring matching fallback
|
|
7
|
+
|
|
8
|
+
This module re-exports the shared search components from gobby.search
|
|
9
|
+
and adds memory-specific TextSearcher backend.
|
|
10
|
+
|
|
11
|
+
Usage:
|
|
12
|
+
from gobby.memory.search import SearchBackend, get_search_backend
|
|
13
|
+
|
|
14
|
+
backend = get_search_backend("tfidf")
|
|
15
|
+
backend.fit([(id, content) for id, content in memories])
|
|
16
|
+
results = backend.search("query text", top_k=10)
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
from typing import TYPE_CHECKING, Any, cast
|
|
22
|
+
|
|
23
|
+
# Re-export shared search components for backwards compatibility
|
|
24
|
+
from gobby.search import SearchBackend, SearchResult, TFIDFSearcher
|
|
25
|
+
|
|
26
|
+
if TYPE_CHECKING:
|
|
27
|
+
from gobby.storage.database import DatabaseProtocol
|
|
28
|
+
|
|
29
|
+
__all__ = [
|
|
30
|
+
"SearchBackend",
|
|
31
|
+
"SearchResult",
|
|
32
|
+
"TFIDFSearcher",
|
|
33
|
+
"get_search_backend",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def get_search_backend(
|
|
38
|
+
backend_type: str,
|
|
39
|
+
db: DatabaseProtocol | None = None,
|
|
40
|
+
**kwargs: Any,
|
|
41
|
+
) -> SearchBackend:
|
|
42
|
+
"""
|
|
43
|
+
Factory function for search backends.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
backend_type: Type of backend - "tfidf" or "text"
|
|
47
|
+
db: Database connection (unused, kept for backwards compatibility)
|
|
48
|
+
**kwargs: Backend-specific configuration
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
SearchBackend instance
|
|
52
|
+
|
|
53
|
+
Raises:
|
|
54
|
+
ValueError: If backend_type is unknown
|
|
55
|
+
ImportError: If required dependencies are not installed
|
|
56
|
+
"""
|
|
57
|
+
if backend_type == "tfidf":
|
|
58
|
+
return cast(SearchBackend, TFIDFSearcher(**kwargs))
|
|
59
|
+
|
|
60
|
+
elif backend_type == "text":
|
|
61
|
+
from gobby.memory.search.text import TextSearcher
|
|
62
|
+
|
|
63
|
+
return cast(SearchBackend, TextSearcher(**kwargs))
|
|
64
|
+
|
|
65
|
+
else:
|
|
66
|
+
raise ValueError(f"Unknown search backend: {backend_type}. Valid options: tfidf, text")
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Simple text-based search backend (fallback).
|
|
3
|
+
|
|
4
|
+
Provides basic substring matching when no other backend is available.
|
|
5
|
+
This is the fallback when sklearn is not installed.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TextSearcher:
|
|
12
|
+
"""
|
|
13
|
+
Simple text-based search using substring matching.
|
|
14
|
+
|
|
15
|
+
This is a fallback backend that works without any dependencies.
|
|
16
|
+
It provides basic relevance scoring based on:
|
|
17
|
+
- Exact phrase match (highest score)
|
|
18
|
+
- Word overlap (proportional score)
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self) -> None:
|
|
22
|
+
self._memories: dict[str, str] = {} # id -> content
|
|
23
|
+
self._fitted = False
|
|
24
|
+
|
|
25
|
+
def fit(self, memories: list[tuple[str, str]]) -> None:
|
|
26
|
+
"""
|
|
27
|
+
Build index from memories.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
memories: List of (memory_id, content) tuples
|
|
31
|
+
"""
|
|
32
|
+
self._memories = {mid: content.lower() for mid, content in memories}
|
|
33
|
+
self._fitted = True
|
|
34
|
+
|
|
35
|
+
def search(self, query: str, top_k: int = 10) -> list[tuple[str, float]]:
|
|
36
|
+
"""
|
|
37
|
+
Search for memories containing query terms.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
query: Search query text
|
|
41
|
+
top_k: Maximum results to return
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
List of (memory_id, similarity_score) tuples
|
|
45
|
+
"""
|
|
46
|
+
if not self._fitted or not self._memories:
|
|
47
|
+
return []
|
|
48
|
+
|
|
49
|
+
query_lower = query.lower()
|
|
50
|
+
query_words = set(query_lower.split())
|
|
51
|
+
results: list[tuple[str, float]] = []
|
|
52
|
+
|
|
53
|
+
for memory_id, content in self._memories.items():
|
|
54
|
+
score = self._score_match(query_lower, query_words, content)
|
|
55
|
+
if score > 0:
|
|
56
|
+
results.append((memory_id, score))
|
|
57
|
+
|
|
58
|
+
# Sort by score descending
|
|
59
|
+
results.sort(key=lambda x: x[1], reverse=True)
|
|
60
|
+
return results[:top_k]
|
|
61
|
+
|
|
62
|
+
def _score_match(
|
|
63
|
+
self,
|
|
64
|
+
query: str,
|
|
65
|
+
query_words: set[str],
|
|
66
|
+
content: str,
|
|
67
|
+
) -> float:
|
|
68
|
+
"""
|
|
69
|
+
Score a content match against query.
|
|
70
|
+
|
|
71
|
+
Scoring:
|
|
72
|
+
- Exact phrase match: 1.0
|
|
73
|
+
- All words present: 0.8
|
|
74
|
+
- Partial word match: proportion of words found * 0.6
|
|
75
|
+
"""
|
|
76
|
+
# Exact phrase match
|
|
77
|
+
if query in content:
|
|
78
|
+
return 1.0
|
|
79
|
+
|
|
80
|
+
# Word-based matching
|
|
81
|
+
content_words = set(content.split())
|
|
82
|
+
matching_words = query_words & content_words
|
|
83
|
+
|
|
84
|
+
if not matching_words:
|
|
85
|
+
return 0.0
|
|
86
|
+
|
|
87
|
+
# All words present
|
|
88
|
+
if matching_words == query_words:
|
|
89
|
+
return 0.8
|
|
90
|
+
|
|
91
|
+
# Partial match - proportion of query words found
|
|
92
|
+
match_ratio = len(matching_words) / len(query_words)
|
|
93
|
+
return match_ratio * 0.6
|
|
94
|
+
|
|
95
|
+
def needs_refit(self) -> bool:
|
|
96
|
+
"""Check if index needs rebuilding."""
|
|
97
|
+
return not self._fitted
|
|
98
|
+
|
|
99
|
+
def add_memory(self, memory_id: str, content: str) -> None:
|
|
100
|
+
"""
|
|
101
|
+
Add a single memory to the index (incremental update).
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
memory_id: Memory ID
|
|
105
|
+
content: Memory content
|
|
106
|
+
"""
|
|
107
|
+
self._memories[memory_id] = content.lower()
|
|
108
|
+
|
|
109
|
+
def remove_memory(self, memory_id: str) -> bool:
|
|
110
|
+
"""
|
|
111
|
+
Remove a memory from the index.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
memory_id: Memory ID to remove
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
True if memory was removed, False if not found
|
|
118
|
+
"""
|
|
119
|
+
if memory_id in self._memories:
|
|
120
|
+
del self._memories[memory_id]
|
|
121
|
+
return True
|
|
122
|
+
return False
|
|
123
|
+
|
|
124
|
+
def clear(self) -> None:
|
|
125
|
+
"""Clear the search index."""
|
|
126
|
+
self._memories.clear()
|
|
127
|
+
self._fitted = False
|