velune-cli 0.9.0__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.
Files changed (279) hide show
  1. velune/__init__.py +5 -0
  2. velune/__main__.py +6 -0
  3. velune/cli/__init__.py +5 -0
  4. velune/cli/app.py +208 -0
  5. velune/cli/autocomplete.py +80 -0
  6. velune/cli/banner.py +60 -0
  7. velune/cli/commands/__init__.py +32 -0
  8. velune/cli/commands/ask.py +175 -0
  9. velune/cli/commands/base.py +16 -0
  10. velune/cli/commands/chat.py +228 -0
  11. velune/cli/commands/config.py +224 -0
  12. velune/cli/commands/daemon.py +88 -0
  13. velune/cli/commands/doctor.py +721 -0
  14. velune/cli/commands/init.py +170 -0
  15. velune/cli/commands/mcp.py +82 -0
  16. velune/cli/commands/memory.py +293 -0
  17. velune/cli/commands/models.py +683 -0
  18. velune/cli/commands/preflight.py +95 -0
  19. velune/cli/commands/run.py +270 -0
  20. velune/cli/commands/setup.py +184 -0
  21. velune/cli/commands/workspace.py +249 -0
  22. velune/cli/context.py +36 -0
  23. velune/cli/councilmodel_ui.py +199 -0
  24. velune/cli/display/council_view.py +254 -0
  25. velune/cli/display/memory_view.py +126 -0
  26. velune/cli/display/panels.py +35 -0
  27. velune/cli/display/progress.py +25 -0
  28. velune/cli/display/themes.py +25 -0
  29. velune/cli/main.py +15 -0
  30. velune/cli/model_selector.py +51 -0
  31. velune/cli/modes.py +86 -0
  32. velune/cli/pull_ui.py +123 -0
  33. velune/cli/registry.py +80 -0
  34. velune/cli/rendering/__init__.py +5 -0
  35. velune/cli/rendering/error_panel.py +79 -0
  36. velune/cli/rendering/markdown.py +63 -0
  37. velune/cli/repl.py +1855 -0
  38. velune/cli/session_manager.py +71 -0
  39. velune/cli/slash_commands.py +37 -0
  40. velune/cli/theme.py +8 -0
  41. velune/cognition/__init__.py +23 -0
  42. velune/cognition/agents/__init__.py +7 -0
  43. velune/cognition/agents/coder.py +209 -0
  44. velune/cognition/agents/planner.py +156 -0
  45. velune/cognition/agents/reviewer.py +195 -0
  46. velune/cognition/arbitrator.py +220 -0
  47. velune/cognition/architecture.py +415 -0
  48. velune/cognition/budget.py +65 -0
  49. velune/cognition/council/__init__.py +47 -0
  50. velune/cognition/council/base.py +217 -0
  51. velune/cognition/council/challenger.py +74 -0
  52. velune/cognition/council/coder.py +79 -0
  53. velune/cognition/council/critic_agent.py +43 -0
  54. velune/cognition/council/critic_configs.py +111 -0
  55. velune/cognition/council/critics.py +41 -0
  56. velune/cognition/council/debate.py +46 -0
  57. velune/cognition/council/factory.py +140 -0
  58. velune/cognition/council/messages.py +56 -0
  59. velune/cognition/council/planner.py +124 -0
  60. velune/cognition/council/reviewer.py +74 -0
  61. velune/cognition/council/synthesizer.py +67 -0
  62. velune/cognition/council/tiers.py +188 -0
  63. velune/cognition/council_orchestrator.py +282 -0
  64. velune/cognition/firewall.py +354 -0
  65. velune/cognition/module.py +46 -0
  66. velune/cognition/orchestrator.py +1205 -0
  67. velune/cognition/personality.py +238 -0
  68. velune/cognition/state.py +104 -0
  69. velune/cognition/style_resolver.py +64 -0
  70. velune/cognition/verification.py +205 -0
  71. velune/context/__init__.py +28 -0
  72. velune/context/assembler.py +240 -0
  73. velune/context/budget.py +97 -0
  74. velune/context/extractive.py +95 -0
  75. velune/context/prompt_adaptation.py +480 -0
  76. velune/context/sections.py +99 -0
  77. velune/context/token_counter.py +134 -0
  78. velune/context/utilization.py +33 -0
  79. velune/context/window.py +63 -0
  80. velune/core/__init__.py +89 -0
  81. velune/core/background.py +5 -0
  82. velune/core/config/__init__.py +37 -0
  83. velune/core/errors/__init__.py +90 -0
  84. velune/core/errors/catalog.py +188 -0
  85. velune/core/errors/execution.py +31 -0
  86. velune/core/errors/memory.py +25 -0
  87. velune/core/errors/orchestration.py +31 -0
  88. velune/core/errors/provider.py +37 -0
  89. velune/core/event_loop.py +35 -0
  90. velune/core/logging.py +83 -0
  91. velune/core/paths.py +165 -0
  92. velune/core/runtime.py +113 -0
  93. velune/core/startup_profiler.py +56 -0
  94. velune/core/task_registry.py +117 -0
  95. velune/core/trace.py +83 -0
  96. velune/core/types/__init__.py +48 -0
  97. velune/core/types/agent.py +53 -0
  98. velune/core/types/context.py +42 -0
  99. velune/core/types/inference.py +38 -0
  100. velune/core/types/memory.py +42 -0
  101. velune/core/types/model.py +70 -0
  102. velune/core/types/provider.py +62 -0
  103. velune/core/types/repository.py +38 -0
  104. velune/core/types/task.py +61 -0
  105. velune/core/types/workspace.py +28 -0
  106. velune/daemon/client.py +13 -0
  107. velune/daemon/server.py +127 -0
  108. velune/daemon/transport.py +179 -0
  109. velune/events.py +204 -0
  110. velune/execution/__init__.py +22 -0
  111. velune/execution/benchmarker.py +315 -0
  112. velune/execution/cancellation.py +53 -0
  113. velune/execution/checkpointer.py +130 -0
  114. velune/execution/command_spec.py +165 -0
  115. velune/execution/diff_preview.py +197 -0
  116. velune/execution/executor.py +181 -0
  117. velune/execution/module.py +18 -0
  118. velune/execution/multi_diff.py +67 -0
  119. velune/execution/path_guard.py +74 -0
  120. velune/execution/planner.py +91 -0
  121. velune/execution/rollback.py +89 -0
  122. velune/execution/sandbox.py +268 -0
  123. velune/execution/validator.py +115 -0
  124. velune/hardware/__init__.py +1 -0
  125. velune/hardware/detector.py +192 -0
  126. velune/kernel/__init__.py +55 -0
  127. velune/kernel/bootstrap.py +125 -0
  128. velune/kernel/config.py +426 -0
  129. velune/kernel/entrypoint.py +78 -0
  130. velune/kernel/health.py +54 -0
  131. velune/kernel/lifecycle.py +143 -0
  132. velune/kernel/module.py +17 -0
  133. velune/kernel/modules.py +23 -0
  134. velune/kernel/registry.py +96 -0
  135. velune/kernel/schemas.py +28 -0
  136. velune/main.py +9 -0
  137. velune/mcp/__init__.py +9 -0
  138. velune/mcp/client.py +115 -0
  139. velune/mcp/config.py +19 -0
  140. velune/mcp/server.py +624 -0
  141. velune/memory/__init__.py +32 -0
  142. velune/memory/compaction.py +506 -0
  143. velune/memory/embedding_pipeline.py +241 -0
  144. velune/memory/lifecycle.py +680 -0
  145. velune/memory/module.py +218 -0
  146. velune/memory/prioritizer.py +67 -0
  147. velune/memory/storage/episodic_schema.sql +53 -0
  148. velune/memory/storage/lancedb_store.py +282 -0
  149. velune/memory/storage/sqlite_manager.py +369 -0
  150. velune/memory/storage/sqlite_pool.py +149 -0
  151. velune/memory/tiers/episodic.py +588 -0
  152. velune/memory/tiers/graph.py +378 -0
  153. velune/memory/tiers/lineage.py +416 -0
  154. velune/memory/tiers/semantic.py +475 -0
  155. velune/memory/tiers/working.py +168 -0
  156. velune/memory/vitality.py +132 -0
  157. velune/models/__init__.py +15 -0
  158. velune/models/family.py +76 -0
  159. velune/models/module.py +20 -0
  160. velune/models/probes.py +192 -0
  161. velune/models/profile_cache.py +84 -0
  162. velune/models/profiler.py +108 -0
  163. velune/models/registry.py +251 -0
  164. velune/models/scorer.py +233 -0
  165. velune/models/specializations.py +205 -0
  166. velune/orchestration/__init__.py +19 -0
  167. velune/orchestration/engine.py +239 -0
  168. velune/orchestration/module.py +15 -0
  169. velune/orchestration/role_assignments.py +82 -0
  170. velune/orchestration/schemas.py +98 -0
  171. velune/plugins/__init__.py +20 -0
  172. velune/plugins/hooks.py +50 -0
  173. velune/plugins/loader.py +161 -0
  174. velune/plugins/registry.py +56 -0
  175. velune/plugins/schemas.py +21 -0
  176. velune/providers/__init__.py +23 -0
  177. velune/providers/adapters/anthropic.py +257 -0
  178. velune/providers/adapters/fireworks.py +115 -0
  179. velune/providers/adapters/google.py +234 -0
  180. velune/providers/adapters/groq.py +151 -0
  181. velune/providers/adapters/huggingface.py +210 -0
  182. velune/providers/adapters/llamacpp.py +208 -0
  183. velune/providers/adapters/lmstudio.py +175 -0
  184. velune/providers/adapters/ollama.py +233 -0
  185. velune/providers/adapters/openai.py +213 -0
  186. velune/providers/adapters/openrouter.py +81 -0
  187. velune/providers/adapters/together.py +134 -0
  188. velune/providers/adapters/xai.py +60 -0
  189. velune/providers/base.py +86 -0
  190. velune/providers/benchmarker.py +138 -0
  191. velune/providers/discovery/__init__.py +33 -0
  192. velune/providers/discovery/anthropic.py +79 -0
  193. velune/providers/discovery/benchmarks.py +44 -0
  194. velune/providers/discovery/classifier.py +69 -0
  195. velune/providers/discovery/fireworks.py +95 -0
  196. velune/providers/discovery/gguf.py +88 -0
  197. velune/providers/discovery/google.py +95 -0
  198. velune/providers/discovery/gpu.py +117 -0
  199. velune/providers/discovery/groq.py +21 -0
  200. velune/providers/discovery/huggingface.py +67 -0
  201. velune/providers/discovery/lmstudio.py +80 -0
  202. velune/providers/discovery/ollama.py +162 -0
  203. velune/providers/discovery/openai.py +96 -0
  204. velune/providers/discovery/openrouter.py +113 -0
  205. velune/providers/discovery/scanner.py +115 -0
  206. velune/providers/discovery/together.py +114 -0
  207. velune/providers/discovery/xai.py +57 -0
  208. velune/providers/health.py +67 -0
  209. velune/providers/health_monitor.py +169 -0
  210. velune/providers/keystore.py +142 -0
  211. velune/providers/local_paths.py +49 -0
  212. velune/providers/local_resolver.py +229 -0
  213. velune/providers/module.py +51 -0
  214. velune/providers/ollama_manager.py +193 -0
  215. velune/providers/registry.py +220 -0
  216. velune/providers/router.py +255 -0
  217. velune/providers/task_classifier.py +288 -0
  218. velune/py.typed +0 -0
  219. velune/repository/__init__.py +33 -0
  220. velune/repository/analyzer.py +127 -0
  221. velune/repository/ast_parser.py +822 -0
  222. velune/repository/blast_radius.py +298 -0
  223. velune/repository/boundary_classifier.py +295 -0
  224. velune/repository/cognition.py +316 -0
  225. velune/repository/grapher.py +179 -0
  226. velune/repository/import_graph.py +263 -0
  227. velune/repository/incremental_indexer.py +275 -0
  228. velune/repository/index_state.py +96 -0
  229. velune/repository/indexer.py +243 -0
  230. velune/repository/module.py +17 -0
  231. velune/repository/parser.py +474 -0
  232. velune/repository/project_type.py +300 -0
  233. velune/repository/rename_journal.py +287 -0
  234. velune/repository/scanner.py +193 -0
  235. velune/repository/schemas.py +102 -0
  236. velune/repository/symbol_registry.py +365 -0
  237. velune/repository/tracker.py +252 -0
  238. velune/retrieval/__init__.py +27 -0
  239. velune/retrieval/cache.py +110 -0
  240. velune/retrieval/fast_path.py +391 -0
  241. velune/retrieval/graph.py +124 -0
  242. velune/retrieval/hybrid.py +271 -0
  243. velune/retrieval/keyword.py +131 -0
  244. velune/retrieval/module.py +26 -0
  245. velune/retrieval/pipeline.py +303 -0
  246. velune/retrieval/reranker.py +102 -0
  247. velune/retrieval/schemas.py +59 -0
  248. velune/retrieval/slow_path.py +364 -0
  249. velune/retrieval/vector.py +203 -0
  250. velune/telemetry/__init__.py +59 -0
  251. velune/telemetry/cognition.py +267 -0
  252. velune/telemetry/cost_estimator.py +92 -0
  253. velune/telemetry/debug.py +304 -0
  254. velune/telemetry/doctor.py +244 -0
  255. velune/telemetry/logging.py +286 -0
  256. velune/telemetry/spans.py +277 -0
  257. velune/telemetry/token_tracker.py +140 -0
  258. velune/telemetry/usage_tracker.py +340 -0
  259. velune/tools/__init__.py +41 -0
  260. velune/tools/base/registry.py +87 -0
  261. velune/tools/base/tool.py +63 -0
  262. velune/tools/code/navigate.py +116 -0
  263. velune/tools/code/search.py +123 -0
  264. velune/tools/filesystem/read.py +75 -0
  265. velune/tools/filesystem/search.py +136 -0
  266. velune/tools/filesystem/write.py +163 -0
  267. velune/tools/git/history.py +177 -0
  268. velune/tools/git/operations.py +122 -0
  269. velune/tools/git/state.py +121 -0
  270. velune/tools/module.py +81 -0
  271. velune/tools/terminal/execute.py +72 -0
  272. velune/tools/terminal/history.py +47 -0
  273. velune/tools/web/fetch.py +55 -0
  274. velune/tools/web/validator.py +122 -0
  275. velune_cli-0.9.0.dist-info/METADATA +518 -0
  276. velune_cli-0.9.0.dist-info/RECORD +279 -0
  277. velune_cli-0.9.0.dist-info/WHEEL +4 -0
  278. velune_cli-0.9.0.dist-info/entry_points.txt +2 -0
  279. velune_cli-0.9.0.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,241 @@
1
+ """Embedding pipeline: content preparation → Ollama nomic-embed-text → LanceDB upsert.
2
+
3
+ Architecture
4
+ ------------
5
+ * ``embed_text / embed_batch`` — synchronous embedding (used for search queries).
6
+ * ``enqueue`` — non-blocking: drops a turn onto a bounded async queue and returns
7
+ immediately. The REPL never waits for an embedding call to complete.
8
+ * Background worker — drains the queue, calls Ollama, upserts to LanceDB.
9
+ Applies exponential back-off (1 → 2 → 4 … 60 s) when Ollama is unavailable,
10
+ then re-queues the item for a later retry.
11
+
12
+ Content preparation before embedding
13
+ -------------------------------------
14
+ 1. Code blocks longer than 200 tokens are replaced with a ``[lang block, N tokens]``
15
+ placeholder — raw code degrades embedding quality without adding recall value.
16
+ 2. A context prefix is prepended: ``"Session YYYY-MM-DD, {role}: "``.
17
+ 3. The combined text is truncated to 512 tokens (embedding quality falls beyond
18
+ this length for nomic-embed-text).
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import asyncio
24
+ import logging
25
+ import re
26
+ from dataclasses import dataclass
27
+ from typing import Any
28
+
29
+ from velune.context.window import estimate_tokens
30
+
31
+ logger = logging.getLogger("velune.memory.embedding_pipeline")
32
+
33
+ _TOKEN_LIMIT = 512
34
+ _CODE_TOKEN_LIMIT = 200
35
+ _BATCH_CONCURRENCY = 3 # max simultaneous Ollama embed calls
36
+ _BACKOFF_BASE = 1.0
37
+ _BACKOFF_MAX = 60.0
38
+ _QUEUE_MAXSIZE = 1_000
39
+
40
+
41
+ # ── Queue item ────────────────────────────────────────────────────────────────
42
+
43
+
44
+ @dataclass
45
+ class EmbedQueueItem:
46
+ """One unit of embedding work enqueued for background processing."""
47
+
48
+ record_id: str
49
+ turn_id: str
50
+ session_id: str
51
+ role: str
52
+ content: str
53
+ source_type: str
54
+ workspace_root: str
55
+ created_at: float
56
+
57
+
58
+ # ── Content preparation ───────────────────────────────────────────────────────
59
+
60
+
61
+ def _strip_long_code_blocks(text: str) -> str:
62
+ """Replace ```lang ... ``` blocks that exceed ``_CODE_TOKEN_LIMIT`` tokens."""
63
+
64
+ def _replace(match: re.Match) -> str:
65
+ block = match.group(0)
66
+ toks = estimate_tokens(block)
67
+ if toks > _CODE_TOKEN_LIMIT:
68
+ lang = (match.group(1) or "code").strip() or "code"
69
+ return f"[{lang} block, {toks} tokens]"
70
+ return block
71
+
72
+ return re.sub(r"```(\w*)\n.*?```", _replace, text, flags=re.DOTALL)
73
+
74
+
75
+ def _prepare_content(content: str, role: str, created_at: float) -> str:
76
+ """Return a semantics-friendly, token-capped version of *content*.
77
+
78
+ 1. Strip long code blocks.
79
+ 2. Prepend ``"Session {date}, {role}: "``.
80
+ 3. Truncate so the total fits inside ``_TOKEN_LIMIT`` tokens.
81
+ """
82
+ from datetime import datetime
83
+
84
+ date_str = datetime.fromtimestamp(created_at).strftime("%Y-%m-%d")
85
+ prefix = f"Session {date_str}, {role}: "
86
+
87
+ cleaned = _strip_long_code_blocks(content)
88
+
89
+ combined = prefix + cleaned
90
+ if estimate_tokens(combined) <= _TOKEN_LIMIT:
91
+ return combined
92
+
93
+ # Trim the content (never the prefix) to fit the token budget.
94
+ # Use a char-per-token heuristic (4 chars ≈ 1 token) for truncation.
95
+ budget_chars = max(0, (_TOKEN_LIMIT - estimate_tokens(prefix)) * 4)
96
+ return prefix + cleaned[:budget_chars]
97
+
98
+
99
+ # ── Pipeline ──────────────────────────────────────────────────────────────────
100
+
101
+
102
+ class EmbeddingPipeline:
103
+ """Async embedding pipeline backed by a provider that supports ``embed()``.
104
+
105
+ Typical provider: ``OllamaProvider`` with ``nomic-embed-text``.
106
+ If *provider* is ``None`` (Ollama unavailable at startup), the pipeline
107
+ degrades gracefully: queries return ``RuntimeError``, queue items accumulate
108
+ until the worker succeeds or the process exits.
109
+
110
+ Lifecycle
111
+ ---------
112
+ ``await pipeline.initialize()`` — start the background worker task.
113
+ ``await pipeline.shutdown()`` — cancel the worker, drain in-flight items.
114
+ """
115
+
116
+ def __init__(
117
+ self,
118
+ provider: Any, # OllamaProvider | None
119
+ store: Any, # LanceDBStore
120
+ model_id: str = "nomic-embed-text",
121
+ ) -> None:
122
+ self._provider = provider
123
+ self._store = store
124
+ self._model_id = model_id
125
+ self._queue: asyncio.Queue[EmbedQueueItem] = asyncio.Queue(maxsize=_QUEUE_MAXSIZE)
126
+ self._worker_task: asyncio.Task | None = None
127
+ self._running = False
128
+ self._backoff = _BACKOFF_BASE
129
+
130
+ # ── Lifecycle ─────────────────────────────────────────────────────────────
131
+
132
+ async def initialize(self) -> None:
133
+ self._running = True
134
+ self._worker_task = asyncio.create_task(
135
+ self._background_worker(), name="velune.embedding_worker"
136
+ )
137
+ logger.debug("EmbeddingPipeline background worker started (model=%s)", self._model_id)
138
+
139
+ async def shutdown(self) -> None:
140
+ self._running = False
141
+ if self._worker_task and not self._worker_task.done():
142
+ self._worker_task.cancel()
143
+ try:
144
+ await self._worker_task
145
+ except asyncio.CancelledError:
146
+ pass
147
+ logger.debug("EmbeddingPipeline background worker stopped")
148
+
149
+ # ── Direct embedding (synchronous / on-demand) ────────────────────────────
150
+
151
+ async def embed_text(self, text: str) -> list[float]:
152
+ """Embed a single string. Raises ``RuntimeError`` if provider unavailable."""
153
+ if not self._provider:
154
+ raise RuntimeError("No embedding provider configured")
155
+ results = await self._provider.embed([text], self._model_id)
156
+ return results[0]
157
+
158
+ async def embed_batch(self, texts: list[str]) -> list[list[float]]:
159
+ """Embed multiple strings with bounded concurrency."""
160
+ if not self._provider:
161
+ raise RuntimeError("No embedding provider configured")
162
+ sem = asyncio.Semaphore(_BATCH_CONCURRENCY)
163
+
164
+ async def _one(t: str) -> list[float]:
165
+ async with sem:
166
+ res = await self._provider.embed([t], self._model_id)
167
+ return res[0]
168
+
169
+ return list(await asyncio.gather(*[_one(t) for t in texts]))
170
+
171
+ # ── Background-queue interface ─────────────────────────────────────────────
172
+
173
+ def enqueue(self, item: EmbedQueueItem) -> None:
174
+ """Non-blocking enqueue. Silently drops the item when the queue is full."""
175
+ try:
176
+ self._queue.put_nowait(item)
177
+ except asyncio.QueueFull:
178
+ logger.warning(
179
+ "Embedding queue full (%d items) — dropping turn %s",
180
+ _QUEUE_MAXSIZE,
181
+ item.turn_id,
182
+ )
183
+
184
+ async def embed_turn(self, item: EmbedQueueItem) -> None:
185
+ """Prepare, embed, and upsert a single turn. Called by the background worker."""
186
+ from velune.memory.storage.lancedb_store import MemoryRecord
187
+
188
+ prepared = _prepare_content(item.content, item.role, item.created_at)
189
+ embedding = await self.embed_text(prepared)
190
+
191
+ record = MemoryRecord(
192
+ id=item.record_id,
193
+ embedding=embedding,
194
+ content=item.content,
195
+ source_type=item.source_type,
196
+ session_id=item.session_id,
197
+ turn_id=item.turn_id,
198
+ workspace_root=item.workspace_root,
199
+ created_at=item.created_at,
200
+ trust_score=1.0,
201
+ )
202
+ await self._store.upsert([record])
203
+ logger.debug(
204
+ "Indexed %s (session=%s, role=%s)",
205
+ item.record_id,
206
+ item.session_id,
207
+ item.role,
208
+ )
209
+
210
+ # ── Background worker ─────────────────────────────────────────────────────
211
+
212
+ async def _background_worker(self) -> None:
213
+ """Drain the queue, retrying with exponential back-off on errors."""
214
+ while self._running:
215
+ # Wait up to 1 second for an item; loop to check _running flag.
216
+ try:
217
+ item = await asyncio.wait_for(self._queue.get(), timeout=1.0)
218
+ except TimeoutError:
219
+ continue
220
+ except asyncio.CancelledError:
221
+ break
222
+
223
+ try:
224
+ await self.embed_turn(item)
225
+ self._backoff = _BACKOFF_BASE # reset on success
226
+ self._queue.task_done()
227
+ except Exception as exc:
228
+ logger.warning(
229
+ "Embedding failed for turn %s (%s) — retry in %.1fs",
230
+ item.turn_id,
231
+ type(exc).__name__,
232
+ self._backoff,
233
+ )
234
+ # Re-enqueue for retry; drop silently if queue is full.
235
+ try:
236
+ self._queue.put_nowait(item)
237
+ except asyncio.QueueFull:
238
+ logger.error("Queue full on retry — permanently dropping turn %s", item.turn_id)
239
+
240
+ await asyncio.sleep(self._backoff)
241
+ self._backoff = min(self._backoff * 2, _BACKOFF_MAX)