kiwime-store 0.1.0__tar.gz
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.
- kiwime_store-0.1.0/.gitignore +78 -0
- kiwime_store-0.1.0/PKG-INFO +9 -0
- kiwime_store-0.1.0/README.md +56 -0
- kiwime_store-0.1.0/migrations/0001_init.sql +203 -0
- kiwime_store-0.1.0/migrations/0002_tombstone_and_id_prefixes.sql +73 -0
- kiwime_store-0.1.0/migrations/0003_skill_profile.sql +67 -0
- kiwime_store-0.1.0/migrations/0004_skill_definition.sql +72 -0
- kiwime_store-0.1.0/migrations/0005_custom_mcp_source.sql +61 -0
- kiwime_store-0.1.0/migrations/0006_vector_store.sql +55 -0
- kiwime_store-0.1.0/migrations/0007_governance_status.sql +50 -0
- kiwime_store-0.1.0/migrations/0008_vector_dim_1024.sql +32 -0
- kiwime_store-0.1.0/migrations/0009_install_config.sql +24 -0
- kiwime_store-0.1.0/pyproject.toml +39 -0
- kiwime_store-0.1.0/src/kiwime_store/__init__.py +12 -0
- kiwime_store-0.1.0/src/kiwime_store/__main__.py +39 -0
- kiwime_store-0.1.0/src/kiwime_store/blob.py +77 -0
- kiwime_store-0.1.0/src/kiwime_store/custom_mcp.py +348 -0
- kiwime_store-0.1.0/src/kiwime_store/db.py +225 -0
- kiwime_store-0.1.0/src/kiwime_store/provider.py +79 -0
- kiwime_store-0.1.0/src/kiwime_store/py.typed +0 -0
- kiwime_store-0.1.0/src/kiwime_store/repo.py +952 -0
- kiwime_store-0.1.0/src/kiwime_store/server.py +709 -0
- kiwime_store-0.1.0/src/kiwime_store/skill.py +354 -0
- kiwime_store-0.1.0/src/kiwime_store/skill_definition.py +481 -0
- kiwime_store-0.1.0/src/kiwime_store/skill_fs_mirror.py +134 -0
- kiwime_store-0.1.0/src/kiwime_store/vector.py +178 -0
- kiwime_store-0.1.0/tests/__init__.py +0 -0
- kiwime_store-0.1.0/tests/conftest.py +27 -0
- kiwime_store-0.1.0/tests/test_blob.py +58 -0
- kiwime_store-0.1.0/tests/test_custom_mcp.py +334 -0
- kiwime_store-0.1.0/tests/test_db.py +83 -0
- kiwime_store-0.1.0/tests/test_provider.py +86 -0
- kiwime_store-0.1.0/tests/test_repo.py +610 -0
- kiwime_store-0.1.0/tests/test_server.py +210 -0
- kiwime_store-0.1.0/tests/test_skill.py +155 -0
- kiwime_store-0.1.0/tests/test_skill_definition.py +276 -0
- kiwime_store-0.1.0/tests/test_skill_fs_mirror.py +109 -0
- kiwime_store-0.1.0/tests/test_vector.py +188 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# OS
|
|
2
|
+
.DS_Store
|
|
3
|
+
Thumbs.db
|
|
4
|
+
|
|
5
|
+
# Editors
|
|
6
|
+
.vscode/*
|
|
7
|
+
!.vscode/settings.json
|
|
8
|
+
.idea/
|
|
9
|
+
*.swp
|
|
10
|
+
*.swo
|
|
11
|
+
|
|
12
|
+
# Rust
|
|
13
|
+
target/
|
|
14
|
+
Cargo.lock.bak
|
|
15
|
+
|
|
16
|
+
# Node
|
|
17
|
+
node_modules/
|
|
18
|
+
dist/
|
|
19
|
+
.pnpm-store/
|
|
20
|
+
*.log
|
|
21
|
+
|
|
22
|
+
# Python
|
|
23
|
+
__pycache__/
|
|
24
|
+
*.py[cod]
|
|
25
|
+
*.egg-info/
|
|
26
|
+
.venv/
|
|
27
|
+
.pytest_cache/
|
|
28
|
+
.mypy_cache/
|
|
29
|
+
.ruff_cache/
|
|
30
|
+
.coverage
|
|
31
|
+
htmlcov/
|
|
32
|
+
|
|
33
|
+
# Tauri
|
|
34
|
+
apps/desktop/src-tauri/target/
|
|
35
|
+
|
|
36
|
+
# Frozen Python sidecars (build artifacts, see ADR 0006)
|
|
37
|
+
apps/desktop/src-tauri/binaries/
|
|
38
|
+
target/pyinstaller/
|
|
39
|
+
|
|
40
|
+
# uv
|
|
41
|
+
.python-version
|
|
42
|
+
|
|
43
|
+
# Frontend build copied into the host package by the publish workflow
|
|
44
|
+
apps/host/src/kiwime_host/_ui/
|
|
45
|
+
|
|
46
|
+
# Local data
|
|
47
|
+
*.sqlite
|
|
48
|
+
*.sqlite-journal
|
|
49
|
+
*.db
|
|
50
|
+
data/
|
|
51
|
+
blobs/
|
|
52
|
+
|
|
53
|
+
# Secrets
|
|
54
|
+
.env
|
|
55
|
+
.env.local
|
|
56
|
+
*.pem
|
|
57
|
+
|
|
58
|
+
# Per-user Claude Code settings (personal permission allowlists, local paths)
|
|
59
|
+
.claude/settings.local.json
|
|
60
|
+
|
|
61
|
+
# TypeScript incremental build state
|
|
62
|
+
*.tsbuildinfo
|
|
63
|
+
apps/desktop/dist-types-node/
|
|
64
|
+
|
|
65
|
+
# Internal planning notes (kept out of the public tree; see docs/ for the
|
|
66
|
+
# published roadmap, ADRs, and release docs)
|
|
67
|
+
.internal-notes/
|
|
68
|
+
docs/release/C_NOTES.md
|
|
69
|
+
docs/release/D_NOTES.md
|
|
70
|
+
docs/release/oss-readiness-review.md
|
|
71
|
+
docs/release/implementation-plan-*.md
|
|
72
|
+
docs/release/webification-plan.md
|
|
73
|
+
|
|
74
|
+
# Tool caches / scratch
|
|
75
|
+
.mypy_cache/
|
|
76
|
+
.pytest_cache/
|
|
77
|
+
.ruff_cache/
|
|
78
|
+
.a1/
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: kiwime-store
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: kiwiMe store sidecar — owns SQLite, the blob store, and the vector store.
|
|
5
|
+
Project-URL: Homepage, https://github.com/kiwiberry-ai/kiwime
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Requires-Python: >=3.11
|
|
8
|
+
Requires-Dist: kiwime-sdk==0.1.0
|
|
9
|
+
Requires-Dist: sqlite-vec>=0.1.9
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# kiwime-store
|
|
2
|
+
|
|
3
|
+
Python sidecar that owns SQLite, the on-disk blob store, and (later) the
|
|
4
|
+
vector store. Other sidecars never touch these files directly — they go
|
|
5
|
+
through this worker over JSON-RPC.
|
|
6
|
+
|
|
7
|
+
See `docs/architecture/ipc-protocol.md` §4 for the wire methods, and
|
|
8
|
+
`docs/schema/core.md` for the record shapes.
|
|
9
|
+
|
|
10
|
+
## Run standalone
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
# Default DB path: ~/Library/Application Support/kiwiMe/store.sqlite (macOS)
|
|
14
|
+
# or $XDG_DATA_HOME/kiwiMe/store.sqlite (Linux).
|
|
15
|
+
python -m kiwime_store --db /tmp/store.sqlite
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
It speaks JSON-RPC 2.0, one frame per line, on stdin/stdout. Logs go to
|
|
19
|
+
stderr only.
|
|
20
|
+
|
|
21
|
+
A trivial smoke test from a shell:
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
printf '%s\n' '{"jsonrpc":"2.0","id":"1","method":"hello"}' \
|
|
25
|
+
| python -m kiwime_store --db /tmp/store.sqlite
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Inspect the DB
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
sqlite3 /tmp/store.sqlite '.tables'
|
|
32
|
+
sqlite3 /tmp/store.sqlite 'SELECT version, name, applied_at FROM schema_migration;'
|
|
33
|
+
sqlite3 /tmp/store.sqlite 'SELECT id, source_id, version FROM document;'
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Blob layout:
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
<db_dir>/blobs/<sha256[:2]>/<sha256>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Methods
|
|
43
|
+
|
|
44
|
+
Implements every `workers/store` method from ipc-protocol.md §4:
|
|
45
|
+
`store.put_record`, `store.get_document`, `store.list_pending_for_compile`,
|
|
46
|
+
`store.put_memory`, `store.put_capability`, `store.put_graph`,
|
|
47
|
+
`store.query_sources`, `store.query_memories`, `store.query_capabilities`,
|
|
48
|
+
`store.query_graph`, `store.migrate`. Plus `store.create_profile` and
|
|
49
|
+
`store.put_source` for bootstrap. All sidecars also expose `hello`,
|
|
50
|
+
`shutdown`, `health` from `kiwime_sdk.Server`.
|
|
51
|
+
|
|
52
|
+
## Tests
|
|
53
|
+
|
|
54
|
+
```sh
|
|
55
|
+
cd workers/store && python -m pytest
|
|
56
|
+
```
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
-- kiwiMe initial schema, v0.
|
|
2
|
+
-- See docs/schema/core.md for the normative contract this implements.
|
|
3
|
+
|
|
4
|
+
PRAGMA foreign_keys = ON;
|
|
5
|
+
PRAGMA journal_mode = WAL;
|
|
6
|
+
|
|
7
|
+
-- ============================================================================
|
|
8
|
+
-- Profiles
|
|
9
|
+
-- ============================================================================
|
|
10
|
+
CREATE TABLE profile (
|
|
11
|
+
id TEXT PRIMARY KEY,
|
|
12
|
+
display_name TEXT NOT NULL,
|
|
13
|
+
created_at TEXT NOT NULL
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
-- ============================================================================
|
|
17
|
+
-- Ingestion layer
|
|
18
|
+
-- ============================================================================
|
|
19
|
+
CREATE TABLE source (
|
|
20
|
+
id TEXT PRIMARY KEY,
|
|
21
|
+
profile_id TEXT NOT NULL REFERENCES profile(id) ON DELETE CASCADE,
|
|
22
|
+
connector TEXT NOT NULL,
|
|
23
|
+
display_name TEXT NOT NULL,
|
|
24
|
+
kind TEXT NOT NULL CHECK (kind IN ('knowledge','activity')),
|
|
25
|
+
scope TEXT NOT NULL, -- JSON
|
|
26
|
+
state TEXT NOT NULL CHECK (state IN ('active','paused','error')) DEFAULT 'active',
|
|
27
|
+
cursor TEXT,
|
|
28
|
+
installed_at TEXT NOT NULL,
|
|
29
|
+
last_sync_at TEXT
|
|
30
|
+
);
|
|
31
|
+
CREATE INDEX idx_source_profile ON source(profile_id);
|
|
32
|
+
|
|
33
|
+
CREATE TABLE document (
|
|
34
|
+
id TEXT PRIMARY KEY,
|
|
35
|
+
source_id TEXT NOT NULL REFERENCES source(id) ON DELETE CASCADE,
|
|
36
|
+
external_id TEXT NOT NULL,
|
|
37
|
+
title TEXT,
|
|
38
|
+
mime_type TEXT NOT NULL,
|
|
39
|
+
text TEXT,
|
|
40
|
+
blob_ref TEXT, -- sha256:<hex>
|
|
41
|
+
metadata TEXT NOT NULL, -- JSON
|
|
42
|
+
created_at TEXT, -- provider-side
|
|
43
|
+
updated_at TEXT NOT NULL,
|
|
44
|
+
ingested_at TEXT NOT NULL,
|
|
45
|
+
version INTEGER NOT NULL DEFAULT 1,
|
|
46
|
+
CHECK (text IS NOT NULL OR blob_ref IS NOT NULL),
|
|
47
|
+
UNIQUE(source_id, external_id)
|
|
48
|
+
);
|
|
49
|
+
CREATE INDEX idx_document_source ON document(source_id);
|
|
50
|
+
CREATE INDEX idx_document_updated ON document(updated_at);
|
|
51
|
+
|
|
52
|
+
CREATE TABLE activity_event (
|
|
53
|
+
id TEXT PRIMARY KEY,
|
|
54
|
+
source_id TEXT NOT NULL REFERENCES source(id) ON DELETE CASCADE,
|
|
55
|
+
kind TEXT NOT NULL,
|
|
56
|
+
actor TEXT,
|
|
57
|
+
subject_ref TEXT, -- optional document id
|
|
58
|
+
payload TEXT NOT NULL, -- JSON
|
|
59
|
+
occurred_at TEXT NOT NULL,
|
|
60
|
+
ingested_at TEXT NOT NULL
|
|
61
|
+
);
|
|
62
|
+
CREATE INDEX idx_event_source ON activity_event(source_id);
|
|
63
|
+
CREATE INDEX idx_event_occurred ON activity_event(occurred_at);
|
|
64
|
+
|
|
65
|
+
CREATE TABLE asset (
|
|
66
|
+
id TEXT PRIMARY KEY,
|
|
67
|
+
source_id TEXT NOT NULL REFERENCES source(id) ON DELETE CASCADE,
|
|
68
|
+
kind TEXT NOT NULL,
|
|
69
|
+
name TEXT NOT NULL,
|
|
70
|
+
description TEXT,
|
|
71
|
+
metadata TEXT NOT NULL -- JSON
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
CREATE TABLE relation_candidate (
|
|
75
|
+
id TEXT PRIMARY KEY,
|
|
76
|
+
from_ref TEXT NOT NULL,
|
|
77
|
+
to_ref TEXT NOT NULL,
|
|
78
|
+
kind TEXT NOT NULL,
|
|
79
|
+
confidence REAL NOT NULL CHECK (confidence BETWEEN 0 AND 1),
|
|
80
|
+
metadata TEXT
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
-- ============================================================================
|
|
84
|
+
-- Compilation layer
|
|
85
|
+
-- ============================================================================
|
|
86
|
+
CREATE TABLE memory (
|
|
87
|
+
id TEXT PRIMARY KEY,
|
|
88
|
+
profile_id TEXT NOT NULL REFERENCES profile(id) ON DELETE CASCADE,
|
|
89
|
+
kind TEXT NOT NULL CHECK (kind IN ('identity','semantic','episodic')),
|
|
90
|
+
text TEXT NOT NULL,
|
|
91
|
+
summary TEXT,
|
|
92
|
+
embedding_ref TEXT NOT NULL, -- pointer into vector store
|
|
93
|
+
confidence REAL NOT NULL CHECK (confidence BETWEEN 0 AND 1),
|
|
94
|
+
created_at TEXT NOT NULL,
|
|
95
|
+
superseded_by TEXT REFERENCES memory(id)
|
|
96
|
+
);
|
|
97
|
+
CREATE INDEX idx_memory_profile ON memory(profile_id);
|
|
98
|
+
CREATE INDEX idx_memory_active ON memory(profile_id, superseded_by) WHERE superseded_by IS NULL;
|
|
99
|
+
|
|
100
|
+
CREATE TABLE capability (
|
|
101
|
+
id TEXT PRIMARY KEY,
|
|
102
|
+
profile_id TEXT NOT NULL REFERENCES profile(id) ON DELETE CASCADE,
|
|
103
|
+
name TEXT NOT NULL,
|
|
104
|
+
description TEXT NOT NULL,
|
|
105
|
+
confidence REAL NOT NULL CHECK (confidence BETWEEN 0 AND 1),
|
|
106
|
+
recency TEXT NOT NULL CHECK (recency IN ('recent','stable','dormant'))
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
CREATE TABLE topic (
|
|
110
|
+
id TEXT PRIMARY KEY,
|
|
111
|
+
profile_id TEXT NOT NULL REFERENCES profile(id) ON DELETE CASCADE,
|
|
112
|
+
label TEXT NOT NULL,
|
|
113
|
+
description TEXT
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
CREATE TABLE topic_member (
|
|
117
|
+
topic_id TEXT NOT NULL REFERENCES topic(id) ON DELETE CASCADE,
|
|
118
|
+
member_ref TEXT NOT NULL,
|
|
119
|
+
PRIMARY KEY (topic_id, member_ref)
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
CREATE TABLE capability_topic (
|
|
123
|
+
capability_id TEXT NOT NULL REFERENCES capability(id) ON DELETE CASCADE,
|
|
124
|
+
topic_id TEXT NOT NULL REFERENCES topic(id) ON DELETE CASCADE,
|
|
125
|
+
PRIMARY KEY (capability_id, topic_id)
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
CREATE TABLE case_record (
|
|
129
|
+
id TEXT PRIMARY KEY,
|
|
130
|
+
profile_id TEXT NOT NULL REFERENCES profile(id) ON DELETE CASCADE,
|
|
131
|
+
title TEXT NOT NULL,
|
|
132
|
+
summary TEXT NOT NULL,
|
|
133
|
+
outcome TEXT,
|
|
134
|
+
confidence REAL NOT NULL CHECK (confidence BETWEEN 0 AND 1),
|
|
135
|
+
status TEXT NOT NULL CHECK (status IN ('candidate','confirmed','dismissed')) DEFAULT 'candidate'
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
CREATE TABLE case_capability (
|
|
139
|
+
case_id TEXT NOT NULL REFERENCES case_record(id) ON DELETE CASCADE,
|
|
140
|
+
capability_id TEXT NOT NULL REFERENCES capability(id) ON DELETE CASCADE,
|
|
141
|
+
PRIMARY KEY (case_id, capability_id)
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
-- ============================================================================
|
|
145
|
+
-- Provenance — links any compilation-layer object to its evidence
|
|
146
|
+
-- ============================================================================
|
|
147
|
+
CREATE TABLE provenance (
|
|
148
|
+
id TEXT PRIMARY KEY,
|
|
149
|
+
target_id TEXT NOT NULL, -- memory/capability/case/edge id
|
|
150
|
+
target_kind TEXT NOT NULL CHECK (target_kind IN ('memory','capability','case','edge')),
|
|
151
|
+
ref TEXT NOT NULL, -- document or event id
|
|
152
|
+
span_start INTEGER,
|
|
153
|
+
span_end INTEGER,
|
|
154
|
+
weight REAL NOT NULL CHECK (weight BETWEEN 0 AND 1)
|
|
155
|
+
);
|
|
156
|
+
CREATE INDEX idx_provenance_target ON provenance(target_id, target_kind);
|
|
157
|
+
CREATE INDEX idx_provenance_ref ON provenance(ref);
|
|
158
|
+
|
|
159
|
+
-- ============================================================================
|
|
160
|
+
-- Knowledge graph (projected from capabilities/topics/cases/assets/sources)
|
|
161
|
+
-- ============================================================================
|
|
162
|
+
CREATE TABLE graph_node (
|
|
163
|
+
id TEXT PRIMARY KEY,
|
|
164
|
+
profile_id TEXT NOT NULL REFERENCES profile(id) ON DELETE CASCADE,
|
|
165
|
+
kind TEXT NOT NULL CHECK (kind IN ('capability','topic','case','asset','source')),
|
|
166
|
+
ref TEXT NOT NULL, -- id of the underlying object
|
|
167
|
+
label TEXT NOT NULL,
|
|
168
|
+
weight REAL NOT NULL DEFAULT 1.0
|
|
169
|
+
);
|
|
170
|
+
CREATE INDEX idx_node_profile ON graph_node(profile_id);
|
|
171
|
+
|
|
172
|
+
CREATE TABLE graph_edge (
|
|
173
|
+
id TEXT PRIMARY KEY,
|
|
174
|
+
profile_id TEXT NOT NULL REFERENCES profile(id) ON DELETE CASCADE,
|
|
175
|
+
from_node TEXT NOT NULL REFERENCES graph_node(id) ON DELETE CASCADE,
|
|
176
|
+
to_node TEXT NOT NULL REFERENCES graph_node(id) ON DELETE CASCADE,
|
|
177
|
+
kind TEXT NOT NULL,
|
|
178
|
+
weight REAL NOT NULL DEFAULT 1.0
|
|
179
|
+
);
|
|
180
|
+
CREATE INDEX idx_edge_profile ON graph_edge(profile_id);
|
|
181
|
+
CREATE INDEX idx_edge_from ON graph_edge(from_node);
|
|
182
|
+
CREATE INDEX idx_edge_to ON graph_edge(to_node);
|
|
183
|
+
|
|
184
|
+
-- ============================================================================
|
|
185
|
+
-- Provider config (per-profile model assignments — see ADR 0004)
|
|
186
|
+
-- ============================================================================
|
|
187
|
+
CREATE TABLE provider_config (
|
|
188
|
+
profile_id TEXT NOT NULL REFERENCES profile(id) ON DELETE CASCADE,
|
|
189
|
+
role TEXT NOT NULL, -- generator | embedder | reranker
|
|
190
|
+
provider TEXT NOT NULL, -- bedrock | ollama | ...
|
|
191
|
+
config TEXT NOT NULL, -- JSON
|
|
192
|
+
PRIMARY KEY (profile_id, role)
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
-- ============================================================================
|
|
196
|
+
-- Schema version (Yoyo-style: just a record of applied migrations)
|
|
197
|
+
-- ============================================================================
|
|
198
|
+
CREATE TABLE schema_migration (
|
|
199
|
+
version INTEGER PRIMARY KEY,
|
|
200
|
+
name TEXT NOT NULL,
|
|
201
|
+
applied_at TEXT NOT NULL
|
|
202
|
+
);
|
|
203
|
+
INSERT INTO schema_migration (version, name, applied_at) VALUES (1, '0001_init', strftime('%Y-%m-%dT%H:%M:%fZ','now'));
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
-- 0002 — Tombstones for soft-delete + id-prefix CHECK constraints.
|
|
2
|
+
--
|
|
3
|
+
-- Why: connectors can re-emit a record set after the source has dropped some
|
|
4
|
+
-- items (file deleted on disk, GitHub issue closed and removed from scope,
|
|
5
|
+
-- Notion page archived). We need to mark them gone without losing provenance
|
|
6
|
+
-- that already cites them. Sweep with ``deleted_at IS NOT NULL`` later.
|
|
7
|
+
--
|
|
8
|
+
-- Why prefix CHECKs: docs/schema/core.md §IDs makes a hard contract that every
|
|
9
|
+
-- id is URL-safe and prefixed. We have been trusting connectors to do the
|
|
10
|
+
-- right thing; lock it at the database boundary so a buggy connector fails
|
|
11
|
+
-- fast at insert time instead of producing a wrong-shaped ``ref`` that tools
|
|
12
|
+
-- elsewhere have to defensively parse. Asset/relation prefixes (ast_/rel_)
|
|
13
|
+
-- are added here because they were missing from the original docs table.
|
|
14
|
+
|
|
15
|
+
PRAGMA foreign_keys = ON;
|
|
16
|
+
|
|
17
|
+
-- ---------------------------------------------------------------------------
|
|
18
|
+
-- Tombstone columns. Existing rows get NULL (i.e. live).
|
|
19
|
+
-- ---------------------------------------------------------------------------
|
|
20
|
+
ALTER TABLE document ADD COLUMN deleted_at TEXT;
|
|
21
|
+
ALTER TABLE activity_event ADD COLUMN deleted_at TEXT;
|
|
22
|
+
ALTER TABLE asset ADD COLUMN deleted_at TEXT;
|
|
23
|
+
ALTER TABLE relation_candidate ADD COLUMN deleted_at TEXT;
|
|
24
|
+
|
|
25
|
+
-- Indexes that match how the compiler scans live rows. Without these, every
|
|
26
|
+
-- compile run would do a full scan and filter ``deleted_at IS NULL`` in app
|
|
27
|
+
-- code; with them, SQLite uses a partial index and skips the tombstoned rows.
|
|
28
|
+
CREATE INDEX idx_document_live ON document(source_id) WHERE deleted_at IS NULL;
|
|
29
|
+
CREATE INDEX idx_event_live ON activity_event(source_id, occurred_at) WHERE deleted_at IS NULL;
|
|
30
|
+
CREATE INDEX idx_asset_live ON asset(source_id) WHERE deleted_at IS NULL;
|
|
31
|
+
CREATE INDEX idx_relation_live ON relation_candidate(from_ref) WHERE deleted_at IS NULL;
|
|
32
|
+
|
|
33
|
+
-- ---------------------------------------------------------------------------
|
|
34
|
+
-- ID-prefix CHECK constraints.
|
|
35
|
+
--
|
|
36
|
+
-- SQLite can't ALTER TABLE ADD CONSTRAINT, so for tables where we want to
|
|
37
|
+
-- enforce a prefix we rebuild the table. We do this for the *new* tables
|
|
38
|
+
-- (asset, relation_candidate) where prefix conventions weren't enforced
|
|
39
|
+
-- before; the older tables (document, activity_event, source) are left as-is
|
|
40
|
+
-- to avoid a costly rebuild — connectors already prefix their ids correctly,
|
|
41
|
+
-- and the contract is documented. New tables built from here on must include
|
|
42
|
+
-- the CHECK at creation time.
|
|
43
|
+
-- ---------------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
CREATE TABLE asset_new (
|
|
46
|
+
id TEXT PRIMARY KEY CHECK (id LIKE 'ast_%'),
|
|
47
|
+
source_id TEXT NOT NULL REFERENCES source(id) ON DELETE CASCADE,
|
|
48
|
+
kind TEXT NOT NULL,
|
|
49
|
+
name TEXT NOT NULL,
|
|
50
|
+
description TEXT,
|
|
51
|
+
metadata TEXT NOT NULL,
|
|
52
|
+
deleted_at TEXT
|
|
53
|
+
);
|
|
54
|
+
INSERT INTO asset_new (id, source_id, kind, name, description, metadata, deleted_at)
|
|
55
|
+
SELECT id, source_id, kind, name, description, metadata, deleted_at FROM asset;
|
|
56
|
+
DROP TABLE asset;
|
|
57
|
+
ALTER TABLE asset_new RENAME TO asset;
|
|
58
|
+
CREATE INDEX idx_asset_live ON asset(source_id) WHERE deleted_at IS NULL;
|
|
59
|
+
|
|
60
|
+
CREATE TABLE relation_candidate_new (
|
|
61
|
+
id TEXT PRIMARY KEY CHECK (id LIKE 'rel_%'),
|
|
62
|
+
from_ref TEXT NOT NULL,
|
|
63
|
+
to_ref TEXT NOT NULL,
|
|
64
|
+
kind TEXT NOT NULL,
|
|
65
|
+
confidence REAL NOT NULL CHECK (confidence BETWEEN 0 AND 1),
|
|
66
|
+
metadata TEXT,
|
|
67
|
+
deleted_at TEXT
|
|
68
|
+
);
|
|
69
|
+
INSERT INTO relation_candidate_new (id, from_ref, to_ref, kind, confidence, metadata, deleted_at)
|
|
70
|
+
SELECT id, from_ref, to_ref, kind, confidence, metadata, deleted_at FROM relation_candidate;
|
|
71
|
+
DROP TABLE relation_candidate;
|
|
72
|
+
ALTER TABLE relation_candidate_new RENAME TO relation_candidate;
|
|
73
|
+
CREATE INDEX idx_relation_live ON relation_candidate(from_ref) WHERE deleted_at IS NULL;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
-- 0003 — Skill Profile + Skill Manager (B1).
|
|
2
|
+
--
|
|
3
|
+
-- Why: A2 (MCP server) + A3 (MCP client) gave kiwiMe a dual MCP role,
|
|
4
|
+
-- but the *configuration* of those — which external MCP servers to ingest
|
|
5
|
+
-- from, what model to compile with, what scope to apply — has no schema
|
|
6
|
+
-- yet. This migration adds skill profiles so users can save and switch
|
|
7
|
+
-- between named bundles of (model preference + MCP servers + source scope).
|
|
8
|
+
--
|
|
9
|
+
-- A "skill profile" is NOT the same as ``profile`` (which represents the
|
|
10
|
+
-- person whose context is being compiled). One person has one ``profile``
|
|
11
|
+
-- and many ``skill_profile`` rows ("Backend FDE skill", "Demo prep skill",
|
|
12
|
+
-- "Personal-research skill"). At compile / export time the app picks
|
|
13
|
+
-- exactly one skill_profile to drive provider selection and MCP-source
|
|
14
|
+
-- inclusion.
|
|
15
|
+
|
|
16
|
+
PRAGMA foreign_keys = ON;
|
|
17
|
+
|
|
18
|
+
-- ---------------------------------------------------------------------------
|
|
19
|
+
-- Skill profile: a named bundle the user can switch between.
|
|
20
|
+
-- ---------------------------------------------------------------------------
|
|
21
|
+
CREATE TABLE IF NOT EXISTS skill_profile (
|
|
22
|
+
id TEXT PRIMARY KEY CHECK (id LIKE 'skp_%'),
|
|
23
|
+
profile_id TEXT NOT NULL REFERENCES profile(id) ON DELETE CASCADE,
|
|
24
|
+
name TEXT NOT NULL,
|
|
25
|
+
description TEXT,
|
|
26
|
+
-- ``model_pref`` is JSON: ``{"generator": {"provider": "ollama", ...},
|
|
27
|
+
-- "embedder": {...}}``. Mirrors provider_config but per-skill, so a user
|
|
28
|
+
-- can have one skill that uses Bedrock and another Ollama without
|
|
29
|
+
-- editing global state.
|
|
30
|
+
model_pref TEXT NOT NULL DEFAULT '{}',
|
|
31
|
+
-- ``scope`` is JSON: ``{"include_sources": [source_id, ...],
|
|
32
|
+
-- "exclude_topics": [topic_id, ...]}``. Empty/missing keys mean
|
|
33
|
+
-- "no restriction".
|
|
34
|
+
scope TEXT NOT NULL DEFAULT '{}',
|
|
35
|
+
is_active INTEGER NOT NULL DEFAULT 0,
|
|
36
|
+
created_at TEXT NOT NULL,
|
|
37
|
+
updated_at TEXT NOT NULL,
|
|
38
|
+
UNIQUE (profile_id, name)
|
|
39
|
+
);
|
|
40
|
+
CREATE INDEX IF NOT EXISTS idx_skill_profile_profile ON skill_profile(profile_id);
|
|
41
|
+
-- Partial unique index: at most one active skill per profile. This is
|
|
42
|
+
-- the one place the application *requires* exclusivity, so enforce it
|
|
43
|
+
-- at the DB instead of trusting app code to dedupe.
|
|
44
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_skill_profile_active
|
|
45
|
+
ON skill_profile(profile_id) WHERE is_active = 1;
|
|
46
|
+
|
|
47
|
+
-- ---------------------------------------------------------------------------
|
|
48
|
+
-- MCP servers attached to a skill: third-party MCP endpoints to ingest from.
|
|
49
|
+
-- These map 1:1 to MCPClientConnector install configs (A3).
|
|
50
|
+
-- ---------------------------------------------------------------------------
|
|
51
|
+
CREATE TABLE IF NOT EXISTS skill_mcp_server (
|
|
52
|
+
id TEXT PRIMARY KEY CHECK (id LIKE 'mcp_%'),
|
|
53
|
+
skill_id TEXT NOT NULL REFERENCES skill_profile(id) ON DELETE CASCADE,
|
|
54
|
+
display_name TEXT NOT NULL,
|
|
55
|
+
command TEXT NOT NULL,
|
|
56
|
+
args TEXT NOT NULL DEFAULT '[]', -- JSON array
|
|
57
|
+
-- ``env_secret_refs``: JSON ``{ENV_NAME: {namespace, key}}``. Resolved
|
|
58
|
+
-- against the secret worker at spawn time so cleartext tokens never
|
|
59
|
+
-- live in this table. Cleartext env (non-secret values like a custom
|
|
60
|
+
-- endpoint URL) goes in ``env_plain``.
|
|
61
|
+
env_plain TEXT NOT NULL DEFAULT '{}', -- JSON {str: str}
|
|
62
|
+
env_secret_refs TEXT NOT NULL DEFAULT '{}', -- JSON
|
|
63
|
+
enabled INTEGER NOT NULL DEFAULT 1,
|
|
64
|
+
created_at TEXT NOT NULL,
|
|
65
|
+
UNIQUE (skill_id, display_name)
|
|
66
|
+
);
|
|
67
|
+
CREATE INDEX IF NOT EXISTS idx_skill_mcp_skill ON skill_mcp_server(skill_id);
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
-- 0004 — Skill definitions (importable / AI-generated SKILL.md content).
|
|
2
|
+
--
|
|
3
|
+
-- Why this exists separately from ``skill_profile``:
|
|
4
|
+
-- ``skill_profile`` (0003) is a *configuration bundle* — model pref +
|
|
5
|
+
-- external MCP servers + scope. ``skill_definition`` is the actual
|
|
6
|
+
-- transferable artifact: a SKILL.md document (frontmatter + body) plus
|
|
7
|
+
-- any referenced resource files. Profiles can optionally point at one
|
|
8
|
+
-- definition to "load" it, but a definition is meaningful on its own —
|
|
9
|
+
-- you can have 30 imported skills and only one active profile.
|
|
10
|
+
--
|
|
11
|
+
-- Source kinds:
|
|
12
|
+
-- - ``file_import`` — user picked a single SKILL.md / *.skill.md file.
|
|
13
|
+
-- - ``path_scan`` — recursively scanned a directory and imported each
|
|
14
|
+
-- SKILL.md found there.
|
|
15
|
+
-- - ``ai_generated``— produced by an in-app generator chat using the
|
|
16
|
+
-- active profile's model preference.
|
|
17
|
+
-- - ``builtin`` — shipped with the app; reserved, not used yet.
|
|
18
|
+
--
|
|
19
|
+
-- Format follows the emerging "Anthropic Claude Skills" / Cursor rules
|
|
20
|
+
-- convention: a YAML frontmatter block (name, description, optional
|
|
21
|
+
-- allowed-tools / model / etc) followed by markdown body.
|
|
22
|
+
--
|
|
23
|
+
-- Dedup is by (profile_id, content_sha256). Re-importing the same file
|
|
24
|
+
-- updates ``imported_at`` rather than creating a duplicate row.
|
|
25
|
+
|
|
26
|
+
PRAGMA foreign_keys = ON;
|
|
27
|
+
|
|
28
|
+
CREATE TABLE IF NOT EXISTS skill_definition (
|
|
29
|
+
id TEXT PRIMARY KEY CHECK (id LIKE 'skd_%'),
|
|
30
|
+
profile_id TEXT NOT NULL REFERENCES profile(id) ON DELETE CASCADE,
|
|
31
|
+
name TEXT NOT NULL,
|
|
32
|
+
description TEXT,
|
|
33
|
+
source_kind TEXT NOT NULL CHECK (source_kind IN
|
|
34
|
+
('file_import', 'path_scan', 'ai_generated', 'builtin')),
|
|
35
|
+
-- Original location (file path, scan root, or chat session id). Kept
|
|
36
|
+
-- for traceability and so a "re-import from disk" UX can find the
|
|
37
|
+
-- file again without nagging the user for the path.
|
|
38
|
+
source_uri TEXT,
|
|
39
|
+
-- Markdown body (without frontmatter). Frontmatter goes in its own
|
|
40
|
+
-- column as JSON so we can query it (e.g. "skills allowing Read tool").
|
|
41
|
+
content_md TEXT NOT NULL,
|
|
42
|
+
frontmatter TEXT NOT NULL DEFAULT '{}', -- JSON
|
|
43
|
+
-- Resource references (relative paths next to the SKILL.md, copied
|
|
44
|
+
-- into the blob store for portability):
|
|
45
|
+
-- ``[{"path": "scripts/foo.py", "sha256": "...", "bytes": 123}]``
|
|
46
|
+
resources TEXT NOT NULL DEFAULT '[]', -- JSON array
|
|
47
|
+
-- sha256 of the canonical (frontmatter + body) text — the dedup key.
|
|
48
|
+
content_sha256 TEXT NOT NULL,
|
|
49
|
+
imported_at TEXT NOT NULL,
|
|
50
|
+
updated_at TEXT NOT NULL,
|
|
51
|
+
UNIQUE (profile_id, content_sha256)
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
CREATE INDEX IF NOT EXISTS idx_skill_definition_profile
|
|
55
|
+
ON skill_definition(profile_id);
|
|
56
|
+
CREATE INDEX IF NOT EXISTS idx_skill_definition_source
|
|
57
|
+
ON skill_definition(source_kind);
|
|
58
|
+
|
|
59
|
+
-- ``skill_profile_definition`` is a 0..1 association: a profile can load
|
|
60
|
+
-- one definition. Modeled as a separate table (instead of ALTER TABLE
|
|
61
|
+
-- ADD COLUMN) because SQLite has no ``ALTER TABLE ... IF NOT EXISTS``,
|
|
62
|
+
-- so this keeps the migration safely idempotent. ``UNIQUE(profile_id)``
|
|
63
|
+
-- enforces the at-most-one rule at the DB.
|
|
64
|
+
CREATE TABLE IF NOT EXISTS skill_profile_definition (
|
|
65
|
+
profile_id TEXT NOT NULL REFERENCES skill_profile(id) ON DELETE CASCADE,
|
|
66
|
+
definition_id TEXT NOT NULL REFERENCES skill_definition(id) ON DELETE CASCADE,
|
|
67
|
+
attached_at TEXT NOT NULL,
|
|
68
|
+
PRIMARY KEY (profile_id),
|
|
69
|
+
UNIQUE (profile_id)
|
|
70
|
+
);
|
|
71
|
+
CREATE INDEX IF NOT EXISTS idx_skill_profile_definition_def
|
|
72
|
+
ON skill_profile_definition(definition_id);
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
-- 0005 — Custom MCP source (data source backed by an external MCP server).
|
|
2
|
+
--
|
|
3
|
+
-- A "custom MCP source" lets the user point kiwiMe at any process that
|
|
4
|
+
-- speaks Model Context Protocol over stdio (e.g. ``npx mcp-server-foo``)
|
|
5
|
+
-- and treat its ``resources/list`` + ``resources/read`` output as another
|
|
6
|
+
-- corpus alongside GitHub / Notion / Drive / local files.
|
|
7
|
+
--
|
|
8
|
+
-- This is the *inbound* (mcp-client) half of kiwiMe's MCP dual-role —
|
|
9
|
+
-- the *outbound* (mcp-server) half exposes our compiled assets the other
|
|
10
|
+
-- way around and lives in a different module.
|
|
11
|
+
--
|
|
12
|
+
-- Scheduling
|
|
13
|
+
-- ``sync_interval_minutes`` is the cadence; NULL means "manual only".
|
|
14
|
+
-- ``next_sync_at`` is precomputed on insert/update and on each completed
|
|
15
|
+
-- sync, so the supervisor cron can do a cheap range scan over the
|
|
16
|
+
-- partial index instead of re-deriving deadlines for every row.
|
|
17
|
+
--
|
|
18
|
+
-- Secrets
|
|
19
|
+
-- ``env_secret_refs`` stores opaque pointers (``{"VAR":{"namespace":...,
|
|
20
|
+
-- "key":...}}``) into the secret worker. The plain ``env_plain`` map is
|
|
21
|
+
-- for non-sensitive values (URLs, flags). Both get merged at spawn time
|
|
22
|
+
-- by the connector runtime, so tokens never sit in this DB.
|
|
23
|
+
--
|
|
24
|
+
-- Why a separate table (vs. reusing ``source``)
|
|
25
|
+
-- ``source`` is the unified surface the UI / compile pipeline sees, but
|
|
26
|
+
-- it doesn't know how to spawn child processes. We mirror each row into
|
|
27
|
+
-- ``source`` (with kind='custom_mcp') from the worker layer; this table
|
|
28
|
+
-- owns the spawn config + cron state.
|
|
29
|
+
|
|
30
|
+
PRAGMA foreign_keys = ON;
|
|
31
|
+
|
|
32
|
+
CREATE TABLE IF NOT EXISTS custom_mcp_source (
|
|
33
|
+
id TEXT PRIMARY KEY CHECK (id LIKE 'cms_%'),
|
|
34
|
+
profile_id TEXT NOT NULL REFERENCES profile(id) ON DELETE CASCADE,
|
|
35
|
+
display_name TEXT NOT NULL,
|
|
36
|
+
command TEXT NOT NULL,
|
|
37
|
+
args TEXT NOT NULL DEFAULT '[]', -- JSON array of strings
|
|
38
|
+
env_plain TEXT NOT NULL DEFAULT '{}', -- JSON {VAR: value}
|
|
39
|
+
env_secret_refs TEXT NOT NULL DEFAULT '{}', -- JSON {VAR: {namespace, key}}
|
|
40
|
+
-- NULL = manual sync only. >0 = run every N minutes via supervisor cron.
|
|
41
|
+
sync_interval_minutes INTEGER,
|
|
42
|
+
last_sync_at TEXT,
|
|
43
|
+
next_sync_at TEXT,
|
|
44
|
+
state TEXT NOT NULL DEFAULT 'active'
|
|
45
|
+
CHECK (state IN ('active', 'paused', 'error')),
|
|
46
|
+
last_error TEXT,
|
|
47
|
+
created_at TEXT NOT NULL,
|
|
48
|
+
updated_at TEXT NOT NULL,
|
|
49
|
+
UNIQUE (profile_id, display_name)
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
-- Partial index: the cron loop only cares about active rows that actually
|
|
53
|
+
-- have a deadline. Skipping paused/error/manual rows here keeps the scan
|
|
54
|
+
-- proportional to "how many timed sources are configured" rather than to
|
|
55
|
+
-- the full table.
|
|
56
|
+
CREATE INDEX IF NOT EXISTS idx_custom_mcp_next_sync
|
|
57
|
+
ON custom_mcp_source(next_sync_at)
|
|
58
|
+
WHERE state = 'active' AND next_sync_at IS NOT NULL;
|
|
59
|
+
|
|
60
|
+
CREATE INDEX IF NOT EXISTS idx_custom_mcp_profile
|
|
61
|
+
ON custom_mcp_source(profile_id);
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
-- 0006_vector_store.sql
|
|
2
|
+
--
|
|
3
|
+
-- Real semantic-retrieval storage, replacing the placeholder `emb:{id}:{dim}`
|
|
4
|
+
-- string the compiler used to write into `memory.embedding_ref`. See ADR 0007.
|
|
5
|
+
--
|
|
6
|
+
-- Two tables, by design:
|
|
7
|
+
--
|
|
8
|
+
-- vec_memory -- sqlite-vec virtual table holding the actual float vectors.
|
|
9
|
+
-- vec0 fixes the dimension at table-creation time, so this
|
|
10
|
+
-- table is the canonical 768-dim store (nomic-embed-text,
|
|
11
|
+
-- the default embedder). Only vectors of the canonical
|
|
12
|
+
-- dimension live here and are KNN-searchable.
|
|
13
|
+
--
|
|
14
|
+
-- embedding_meta -- ordinary table recording, per target, which
|
|
15
|
+
-- provider/model/dimension produced the vector, plus a
|
|
16
|
+
-- `needs_reindex` flag. This is the source of truth for
|
|
17
|
+
-- "what produced this embedding" and survives even when a
|
|
18
|
+
-- vector cannot enter vec_memory because its dimension
|
|
19
|
+
-- differs from the canonical store (model switch). It is
|
|
20
|
+
-- also what a future reindex tool reads (Iteration 5).
|
|
21
|
+
--
|
|
22
|
+
-- The vec0 extension must be loaded on the connection before this migration
|
|
23
|
+
-- runs; the store's Database loads it (see db.py). If the extension is
|
|
24
|
+
-- unavailable the CREATE VIRTUAL TABLE fails loudly rather than silently
|
|
25
|
+
-- degrading to keyword-only search.
|
|
26
|
+
|
|
27
|
+
-- Canonical 768-dim vector store. memory_id is the TEXT primary key so we can
|
|
28
|
+
-- upsert/delete by memory id and join back to the `memory` table.
|
|
29
|
+
CREATE VIRTUAL TABLE vec_memory USING vec0(
|
|
30
|
+
memory_id TEXT PRIMARY KEY,
|
|
31
|
+
embedding float[768]
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
-- Per-embedding provenance + lifecycle. One row per (target_id, target_kind).
|
|
35
|
+
-- target_kind is 'memory' today; the column leaves room for embedding other
|
|
36
|
+
-- governed objects later without another migration.
|
|
37
|
+
CREATE TABLE embedding_meta (
|
|
38
|
+
target_id TEXT NOT NULL,
|
|
39
|
+
target_kind TEXT NOT NULL DEFAULT 'memory'
|
|
40
|
+
CHECK (target_kind IN ('memory')),
|
|
41
|
+
provider TEXT NOT NULL, -- e.g. 'ollama', 'openai_compat'
|
|
42
|
+
model TEXT NOT NULL, -- e.g. 'nomic-embed-text', 'bge-m3'
|
|
43
|
+
dimension INTEGER NOT NULL, -- vector length the model produced
|
|
44
|
+
in_vec_store INTEGER NOT NULL DEFAULT 0
|
|
45
|
+
CHECK (in_vec_store IN (0, 1)), -- 1 iff present in vec_memory
|
|
46
|
+
needs_reindex INTEGER NOT NULL DEFAULT 0
|
|
47
|
+
CHECK (needs_reindex IN (0, 1)), -- 1 iff dim != canonical
|
|
48
|
+
created_at TEXT NOT NULL,
|
|
49
|
+
PRIMARY KEY (target_id, target_kind)
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
CREATE INDEX idx_embedding_meta_reindex
|
|
53
|
+
ON embedding_meta(needs_reindex) WHERE needs_reindex = 1;
|
|
54
|
+
CREATE INDEX idx_embedding_meta_model
|
|
55
|
+
ON embedding_meta(provider, model);
|