phorvec 0.1.2__cp38-abi3-win_amd64.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.
- phorvec/__init__.py +135 -0
- phorvec/_cli.py +7 -0
- phorvec/_phorvec.pyd +0 -0
- phorvec/admin.py +281 -0
- phorvec/agents.py +290 -0
- phorvec/branches.py +138 -0
- phorvec/chat.py +192 -0
- phorvec/context.py +690 -0
- phorvec/facts.py +245 -0
- phorvec/graph.py +269 -0
- phorvec/maintenance.py +242 -0
- phorvec/memory.py +246 -0
- phorvec/memory_blocks.py +211 -0
- phorvec/projects.py +193 -0
- phorvec/rag.py +207 -0
- phorvec/skills.py +154 -0
- phorvec/snapshots.py +116 -0
- phorvec/team.py +94 -0
- phorvec/watches.py +115 -0
- phorvec-0.1.2.dist-info/METADATA +129 -0
- phorvec-0.1.2.dist-info/RECORD +25 -0
- phorvec-0.1.2.dist-info/WHEEL +4 -0
- phorvec-0.1.2.dist-info/entry_points.txt +2 -0
- phorvec-0.1.2.dist-info/licenses/LICENSE +18 -0
- phorvec-0.1.2.dist-info/sboms/phorvec.cyclonedx.json +13484 -0
phorvec/__init__.py
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""phorvec — persistent vector memory for AI agents (Python bindings).
|
|
2
|
+
|
|
3
|
+
The low-level engine (`Engine`) exposes every phorvec tool via
|
|
4
|
+
`Engine.call(name, params_json)`. On top of it, each feature family gets an
|
|
5
|
+
ergonomic wrapper class:
|
|
6
|
+
|
|
7
|
+
- `Memory` / `Recall` — durable agent memory (+ core-memory admin: pin, forget,
|
|
8
|
+
rewrite, prioritize, consolidate, export/import, gc, list, promote)
|
|
9
|
+
- `Team` — multi-agent teams (paid tier)
|
|
10
|
+
- `Graph` — knowledge graph (entities, links, queries, stats)
|
|
11
|
+
- `Facts` — temporal facts (supersede/invalidate/reactivate/history)
|
|
12
|
+
- `Skills` — skill library (ingest/search/get/list/sync)
|
|
13
|
+
- `Rag` — retrieval over indexed files/directories
|
|
14
|
+
- `MemoryBlocks` — shared, token-budgeted memory blocks
|
|
15
|
+
- `Context` — working-context store/retrieve + lifecycle ops
|
|
16
|
+
- `Snapshots` — context snapshots (save/restore/list/delete)
|
|
17
|
+
- `Branches` — context branches (create/switch/merge/…)
|
|
18
|
+
- `Watches` — semantic context watches
|
|
19
|
+
- `Agents` — agent versioning, diff/rollback, export/import, templates
|
|
20
|
+
- `Chat` — chat session search/resume
|
|
21
|
+
- `Projects` / `Outcomes` — project lifecycle + outcome tracking
|
|
22
|
+
- `Maintenance` / `Quantization` — buffer/cache/GC ops + vector quantization
|
|
23
|
+
- `Analytics` / `Security` / `Anchors` / `Export` — analytics/metrics, PII scan,
|
|
24
|
+
anchors, obsidian export
|
|
25
|
+
|
|
26
|
+
Every wrapper except `Memory` BORROWS an externally-created `Engine` and does
|
|
27
|
+
not own its lifecycle — construct one `Engine` (or a `Memory`) and layer the
|
|
28
|
+
others on top of the same handle.
|
|
29
|
+
"""
|
|
30
|
+
from ._phorvec import (
|
|
31
|
+
Engine,
|
|
32
|
+
PhorvecError,
|
|
33
|
+
TierRequiredError,
|
|
34
|
+
UnknownToolError,
|
|
35
|
+
run_cli,
|
|
36
|
+
)
|
|
37
|
+
from .memory import Memory, Recall
|
|
38
|
+
from .team import Team
|
|
39
|
+
from .graph import Graph, Entity, GraphStats
|
|
40
|
+
from .facts import Facts, FactHistory, FactCurrentFact, FactEntry
|
|
41
|
+
from .skills import Skills, SkillEntry, SkillDetail, SkillHit
|
|
42
|
+
from .rag import Rag, RagHit
|
|
43
|
+
from .memory_blocks import MemoryBlocks, Block, BlockListResult
|
|
44
|
+
from .context import Context, StoreResult, RetrieveHit, TokenCount
|
|
45
|
+
from .snapshots import Snapshots, SnapshotInfo
|
|
46
|
+
from .branches import Branches, BranchInfo
|
|
47
|
+
from .watches import Watches, Watch, WatchInfo
|
|
48
|
+
from .agents import (
|
|
49
|
+
Agents,
|
|
50
|
+
VersionEntry,
|
|
51
|
+
AgentHistory,
|
|
52
|
+
DiffResult,
|
|
53
|
+
DiffStats,
|
|
54
|
+
Template,
|
|
55
|
+
TemplateVariable,
|
|
56
|
+
)
|
|
57
|
+
from .chat import Chat, SearchHit, SearchSession, Session, Turn, ResumeResult
|
|
58
|
+
from .projects import Projects, Outcomes, ProjectInfo, OutcomeEntry
|
|
59
|
+
from .maintenance import Maintenance, Quantization, BufferStatus
|
|
60
|
+
from .admin import (
|
|
61
|
+
Analytics,
|
|
62
|
+
Security,
|
|
63
|
+
Anchors,
|
|
64
|
+
Export,
|
|
65
|
+
DetectedSecret,
|
|
66
|
+
ScanResult,
|
|
67
|
+
Anchor,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
__all__ = [
|
|
71
|
+
# core
|
|
72
|
+
"Engine",
|
|
73
|
+
"PhorvecError",
|
|
74
|
+
"TierRequiredError",
|
|
75
|
+
"UnknownToolError",
|
|
76
|
+
"run_cli",
|
|
77
|
+
# wrapper classes
|
|
78
|
+
"Memory",
|
|
79
|
+
"Recall",
|
|
80
|
+
"Team",
|
|
81
|
+
"Graph",
|
|
82
|
+
"Entity",
|
|
83
|
+
"GraphStats",
|
|
84
|
+
"Facts",
|
|
85
|
+
"FactHistory",
|
|
86
|
+
"FactCurrentFact",
|
|
87
|
+
"FactEntry",
|
|
88
|
+
"Skills",
|
|
89
|
+
"SkillEntry",
|
|
90
|
+
"SkillDetail",
|
|
91
|
+
"SkillHit",
|
|
92
|
+
"Rag",
|
|
93
|
+
"RagHit",
|
|
94
|
+
"MemoryBlocks",
|
|
95
|
+
"Block",
|
|
96
|
+
"BlockListResult",
|
|
97
|
+
"Context",
|
|
98
|
+
"StoreResult",
|
|
99
|
+
"RetrieveHit",
|
|
100
|
+
"TokenCount",
|
|
101
|
+
"Snapshots",
|
|
102
|
+
"SnapshotInfo",
|
|
103
|
+
"Branches",
|
|
104
|
+
"BranchInfo",
|
|
105
|
+
"Watches",
|
|
106
|
+
"Watch",
|
|
107
|
+
"WatchInfo",
|
|
108
|
+
"Agents",
|
|
109
|
+
"VersionEntry",
|
|
110
|
+
"AgentHistory",
|
|
111
|
+
"DiffResult",
|
|
112
|
+
"DiffStats",
|
|
113
|
+
"Template",
|
|
114
|
+
"TemplateVariable",
|
|
115
|
+
"Chat",
|
|
116
|
+
"SearchHit",
|
|
117
|
+
"SearchSession",
|
|
118
|
+
"Session",
|
|
119
|
+
"Turn",
|
|
120
|
+
"ResumeResult",
|
|
121
|
+
"Projects",
|
|
122
|
+
"Outcomes",
|
|
123
|
+
"ProjectInfo",
|
|
124
|
+
"OutcomeEntry",
|
|
125
|
+
"Maintenance",
|
|
126
|
+
"Quantization",
|
|
127
|
+
"BufferStatus",
|
|
128
|
+
"Analytics",
|
|
129
|
+
"Security",
|
|
130
|
+
"Anchors",
|
|
131
|
+
"Export",
|
|
132
|
+
"DetectedSecret",
|
|
133
|
+
"ScanResult",
|
|
134
|
+
"Anchor",
|
|
135
|
+
]
|
phorvec/_cli.py
ADDED
phorvec/_phorvec.pyd
ADDED
|
Binary file
|
phorvec/admin.py
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
"""Admin tools for analytics, security scanning, anchor management, and vault export.
|
|
2
|
+
|
|
3
|
+
Wraps four singleton tool families: `analytics`/`metrics` (Analytics class),
|
|
4
|
+
`security_scan` (Security class), `anchor_manage` (Anchors class), and
|
|
5
|
+
`obsidian_export` (Export class). All four classes borrow an externally-created
|
|
6
|
+
Engine and do NOT own it — the caller controls the Engine lifecycle.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
from typing import Any, Optional
|
|
13
|
+
|
|
14
|
+
from ._phorvec import Engine
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# ============================================================================
|
|
18
|
+
# Analytics — wraps `analytics` and `metrics` tools
|
|
19
|
+
# ============================================================================
|
|
20
|
+
|
|
21
|
+
class Analytics:
|
|
22
|
+
"""Conversation analytics and token-savings metrics for a phorvec store.
|
|
23
|
+
|
|
24
|
+
`Analytics` BORROWS an externally-created `Engine` and does NOT own it,
|
|
25
|
+
so it has no `close()`/context-manager — the caller owns the Engine.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(self, engine: Engine, *, project: Optional[str] = None):
|
|
29
|
+
self._engine = engine
|
|
30
|
+
self._project = project
|
|
31
|
+
|
|
32
|
+
def _call(self, tool: str, params: dict) -> Any:
|
|
33
|
+
return json.loads(self._engine.call(tool, json.dumps(params)))
|
|
34
|
+
|
|
35
|
+
def query(
|
|
36
|
+
self,
|
|
37
|
+
*,
|
|
38
|
+
time_range: Optional[str] = None,
|
|
39
|
+
format: str = "json",
|
|
40
|
+
breakdown: Optional[bool] = None,
|
|
41
|
+
project: Optional[str] = None,
|
|
42
|
+
) -> Any:
|
|
43
|
+
"""Query conversation analytics (session stats, tool usage, topics, ROI).
|
|
44
|
+
|
|
45
|
+
Returns a dict when format='json' (default) or a str when format='human'.
|
|
46
|
+
time_range ∈ {'today','this_week','this_month','all_time'} (default: all_time).
|
|
47
|
+
breakdown=True gives a per-project breakdown instead of a single summary.
|
|
48
|
+
"""
|
|
49
|
+
p = project or self._project
|
|
50
|
+
params: dict[str, Any] = {"format": format}
|
|
51
|
+
if p is not None:
|
|
52
|
+
params["project"] = p
|
|
53
|
+
if time_range is not None:
|
|
54
|
+
params["time_range"] = time_range
|
|
55
|
+
if breakdown is not None:
|
|
56
|
+
params["breakdown"] = breakdown
|
|
57
|
+
raw = self._engine.call("analytics", json.dumps(params))
|
|
58
|
+
return json.loads(raw) if format == "json" else raw
|
|
59
|
+
|
|
60
|
+
def get(
|
|
61
|
+
self,
|
|
62
|
+
*,
|
|
63
|
+
format: str = "json",
|
|
64
|
+
project: Optional[str] = None,
|
|
65
|
+
start_date: Optional[str] = None,
|
|
66
|
+
end_date: Optional[str] = None,
|
|
67
|
+
) -> Any:
|
|
68
|
+
"""Get token-savings metrics (cumulative, per-project, daily history).
|
|
69
|
+
|
|
70
|
+
Returns a dict when format='json' (default) or a str when format='human'.
|
|
71
|
+
Optionally filter by project and/or date range (YYYY-MM-DD strings).
|
|
72
|
+
"""
|
|
73
|
+
p = project or self._project
|
|
74
|
+
params: dict[str, Any] = {"format": format}
|
|
75
|
+
if p is not None:
|
|
76
|
+
params["project"] = p
|
|
77
|
+
if start_date is not None:
|
|
78
|
+
params["start_date"] = start_date
|
|
79
|
+
if end_date is not None:
|
|
80
|
+
params["end_date"] = end_date
|
|
81
|
+
raw = self._engine.call("metrics", json.dumps(params))
|
|
82
|
+
return json.loads(raw) if format == "json" else raw
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
# ============================================================================
|
|
86
|
+
# Security — wraps `security_scan` tool
|
|
87
|
+
# ============================================================================
|
|
88
|
+
|
|
89
|
+
@dataclass
|
|
90
|
+
class DetectedSecret:
|
|
91
|
+
"""One secret detected during a `security_scan`."""
|
|
92
|
+
type: str
|
|
93
|
+
start: int
|
|
94
|
+
end: int
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@dataclass
|
|
98
|
+
class ScanResult:
|
|
99
|
+
"""Full result of a `security_scan` call."""
|
|
100
|
+
secrets_found: int
|
|
101
|
+
action_taken: str
|
|
102
|
+
policy: str
|
|
103
|
+
clean_content: str
|
|
104
|
+
detected_secrets: list = field(default_factory=list)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class Security:
|
|
108
|
+
"""Secret-scanning tool wrapper. Borrows an externally-created Engine."""
|
|
109
|
+
|
|
110
|
+
def __init__(self, engine: Engine):
|
|
111
|
+
self._engine = engine
|
|
112
|
+
|
|
113
|
+
def _call(self, tool: str, params: dict) -> Any:
|
|
114
|
+
return json.loads(self._engine.call(tool, json.dumps(params)))
|
|
115
|
+
|
|
116
|
+
def scan(self, content: str) -> ScanResult:
|
|
117
|
+
"""Scan content for secrets (API keys, tokens, credentials) without storing it.
|
|
118
|
+
|
|
119
|
+
Requires a non-empty content string. Returns a ScanResult with all findings,
|
|
120
|
+
the action the configured security policy would take, and the redacted content.
|
|
121
|
+
Raises PhorvecError on engine errors; does NOT catch empty-content errors.
|
|
122
|
+
"""
|
|
123
|
+
raw = self._call("security_scan", {"content": content})
|
|
124
|
+
secrets = [
|
|
125
|
+
DetectedSecret(type=s["type"], start=s["start"], end=s["end"])
|
|
126
|
+
for s in raw.get("detected_secrets", [])
|
|
127
|
+
]
|
|
128
|
+
return ScanResult(
|
|
129
|
+
secrets_found=raw["secrets_found"],
|
|
130
|
+
action_taken=raw["action_taken"],
|
|
131
|
+
policy=raw["policy"],
|
|
132
|
+
clean_content=raw["clean_content"],
|
|
133
|
+
detected_secrets=secrets,
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
# ============================================================================
|
|
138
|
+
# Anchors — wraps `anchor_manage` tool
|
|
139
|
+
# ============================================================================
|
|
140
|
+
|
|
141
|
+
@dataclass
|
|
142
|
+
class Anchor:
|
|
143
|
+
"""One semantic anchor returned by `anchor_manage` list."""
|
|
144
|
+
id: str
|
|
145
|
+
content: str
|
|
146
|
+
created_at: int
|
|
147
|
+
source: str
|
|
148
|
+
token_count: int
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
class Anchors:
|
|
152
|
+
"""Manage semantic anchors — key facts that persist across context and resist pruning.
|
|
153
|
+
|
|
154
|
+
`Anchors` BORROWS an externally-created `Engine` and does NOT own it.
|
|
155
|
+
Maximum 20 anchors per project (enforced by the engine).
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
def __init__(self, engine: Engine, *, project: Optional[str] = None):
|
|
159
|
+
self._engine = engine
|
|
160
|
+
self._project = project
|
|
161
|
+
|
|
162
|
+
def _call(self, tool: str, params: dict) -> Any:
|
|
163
|
+
return json.loads(self._engine.call(tool, json.dumps(params)))
|
|
164
|
+
|
|
165
|
+
def _resolve_project(self, project: Optional[str]) -> str:
|
|
166
|
+
p = project or self._project
|
|
167
|
+
if p is None:
|
|
168
|
+
raise ValueError("project is required (pass project= or set it on the wrapper)")
|
|
169
|
+
return p
|
|
170
|
+
|
|
171
|
+
def manage(
|
|
172
|
+
self,
|
|
173
|
+
operation: str,
|
|
174
|
+
*,
|
|
175
|
+
content: Optional[str] = None,
|
|
176
|
+
anchor_id: Optional[str] = None,
|
|
177
|
+
project: Optional[str] = None,
|
|
178
|
+
) -> dict:
|
|
179
|
+
"""Generic anchor_manage call. Prefer the list/add/remove/update helpers.
|
|
180
|
+
|
|
181
|
+
operation ∈ {'list','add','remove','update'}.
|
|
182
|
+
content is required for add and update; anchor_id is required for remove and update.
|
|
183
|
+
Requires project (positional on wrapper, or per-call via project= kwarg).
|
|
184
|
+
"""
|
|
185
|
+
p = self._resolve_project(project)
|
|
186
|
+
params: dict[str, Any] = {"project": p, "operation": operation}
|
|
187
|
+
if content is not None:
|
|
188
|
+
params["content"] = content
|
|
189
|
+
if anchor_id is not None:
|
|
190
|
+
params["anchor_id"] = anchor_id
|
|
191
|
+
return self._call("anchor_manage", params)
|
|
192
|
+
|
|
193
|
+
def list(self, *, project: Optional[str] = None) -> list:
|
|
194
|
+
"""List all anchors for the project. Returns a list of Anchor objects."""
|
|
195
|
+
out = self.manage("list", project=project)
|
|
196
|
+
return [
|
|
197
|
+
Anchor(
|
|
198
|
+
id=a["id"],
|
|
199
|
+
content=a["content"],
|
|
200
|
+
created_at=a.get("created_at", 0),
|
|
201
|
+
source=a.get("source", "manual"),
|
|
202
|
+
token_count=a.get("token_count", 0),
|
|
203
|
+
)
|
|
204
|
+
for a in out.get("anchors", [])
|
|
205
|
+
]
|
|
206
|
+
|
|
207
|
+
def add(self, content: str, *, project: Optional[str] = None) -> dict:
|
|
208
|
+
"""Add a new anchor. Returns {id, content, importance, anchor_count, max_anchors}.
|
|
209
|
+
|
|
210
|
+
Requires content (non-empty string). Raises if the per-project limit (20) is reached.
|
|
211
|
+
"""
|
|
212
|
+
return self.manage("add", content=content, project=project)
|
|
213
|
+
|
|
214
|
+
def remove(self, anchor_id: str, *, project: Optional[str] = None) -> dict:
|
|
215
|
+
"""Remove the anchor flag from anchor_id. Returns {removed, message}.
|
|
216
|
+
|
|
217
|
+
The underlying context item remains; only the anchor designation is cleared.
|
|
218
|
+
Requires anchor_id.
|
|
219
|
+
"""
|
|
220
|
+
return self.manage("remove", anchor_id=anchor_id, project=project)
|
|
221
|
+
|
|
222
|
+
def update(self, anchor_id: str, content: str, *, project: Optional[str] = None) -> dict:
|
|
223
|
+
"""Update the content of an existing anchor. Returns {updated, content, message}.
|
|
224
|
+
|
|
225
|
+
Requires anchor_id (must point to an existing anchor) and new content.
|
|
226
|
+
"""
|
|
227
|
+
return self.manage("update", content=content, anchor_id=anchor_id, project=project)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
# ============================================================================
|
|
231
|
+
# Export — wraps `obsidian_export` tool
|
|
232
|
+
# ============================================================================
|
|
233
|
+
|
|
234
|
+
class Export:
|
|
235
|
+
"""Obsidian vault export for phorvec projects.
|
|
236
|
+
|
|
237
|
+
`Export` BORROWS an externally-created `Engine` and does NOT own it.
|
|
238
|
+
The generated vault is a read-only projection; edits do not flow back.
|
|
239
|
+
"""
|
|
240
|
+
|
|
241
|
+
def __init__(self, engine: Engine, *, project: Optional[str] = None):
|
|
242
|
+
self._engine = engine
|
|
243
|
+
self._project = project
|
|
244
|
+
|
|
245
|
+
def _call(self, tool: str, params: dict) -> Any:
|
|
246
|
+
return json.loads(self._engine.call(tool, json.dumps(params)))
|
|
247
|
+
|
|
248
|
+
def _resolve_project(self, project: Optional[str]) -> str:
|
|
249
|
+
p = project or self._project
|
|
250
|
+
if p is None:
|
|
251
|
+
raise ValueError("project is required (pass project= or set it on the wrapper)")
|
|
252
|
+
return p
|
|
253
|
+
|
|
254
|
+
def obsidian(
|
|
255
|
+
self,
|
|
256
|
+
*,
|
|
257
|
+
vault_root: Optional[str] = None,
|
|
258
|
+
force: Optional[bool] = None,
|
|
259
|
+
preserve_all: Optional[bool] = None,
|
|
260
|
+
dry_run: Optional[bool] = None,
|
|
261
|
+
project: Optional[str] = None,
|
|
262
|
+
) -> dict:
|
|
263
|
+
"""Export the project to an Obsidian markdown vault. Returns an export report dict.
|
|
264
|
+
|
|
265
|
+
Requires project. vault_root defaults to ~/phorvec/vault/<project>/.
|
|
266
|
+
force=True overwrites externally-edited vault files (you lose those edits).
|
|
267
|
+
preserve_all=True silently skips externally-edited files.
|
|
268
|
+
force and preserve_all are mutually exclusive.
|
|
269
|
+
dry_run=True previews what would happen without touching the filesystem.
|
|
270
|
+
"""
|
|
271
|
+
p = self._resolve_project(project)
|
|
272
|
+
params: dict[str, Any] = {"project": p}
|
|
273
|
+
if vault_root is not None:
|
|
274
|
+
params["vault_root"] = vault_root
|
|
275
|
+
if force is not None:
|
|
276
|
+
params["force"] = force
|
|
277
|
+
if preserve_all is not None:
|
|
278
|
+
params["preserve_all"] = preserve_all
|
|
279
|
+
if dry_run is not None:
|
|
280
|
+
params["dry_run"] = dry_run
|
|
281
|
+
return self._call("obsidian_export", params)
|