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.
Files changed (38) hide show
  1. kiwime_store-0.1.0/.gitignore +78 -0
  2. kiwime_store-0.1.0/PKG-INFO +9 -0
  3. kiwime_store-0.1.0/README.md +56 -0
  4. kiwime_store-0.1.0/migrations/0001_init.sql +203 -0
  5. kiwime_store-0.1.0/migrations/0002_tombstone_and_id_prefixes.sql +73 -0
  6. kiwime_store-0.1.0/migrations/0003_skill_profile.sql +67 -0
  7. kiwime_store-0.1.0/migrations/0004_skill_definition.sql +72 -0
  8. kiwime_store-0.1.0/migrations/0005_custom_mcp_source.sql +61 -0
  9. kiwime_store-0.1.0/migrations/0006_vector_store.sql +55 -0
  10. kiwime_store-0.1.0/migrations/0007_governance_status.sql +50 -0
  11. kiwime_store-0.1.0/migrations/0008_vector_dim_1024.sql +32 -0
  12. kiwime_store-0.1.0/migrations/0009_install_config.sql +24 -0
  13. kiwime_store-0.1.0/pyproject.toml +39 -0
  14. kiwime_store-0.1.0/src/kiwime_store/__init__.py +12 -0
  15. kiwime_store-0.1.0/src/kiwime_store/__main__.py +39 -0
  16. kiwime_store-0.1.0/src/kiwime_store/blob.py +77 -0
  17. kiwime_store-0.1.0/src/kiwime_store/custom_mcp.py +348 -0
  18. kiwime_store-0.1.0/src/kiwime_store/db.py +225 -0
  19. kiwime_store-0.1.0/src/kiwime_store/provider.py +79 -0
  20. kiwime_store-0.1.0/src/kiwime_store/py.typed +0 -0
  21. kiwime_store-0.1.0/src/kiwime_store/repo.py +952 -0
  22. kiwime_store-0.1.0/src/kiwime_store/server.py +709 -0
  23. kiwime_store-0.1.0/src/kiwime_store/skill.py +354 -0
  24. kiwime_store-0.1.0/src/kiwime_store/skill_definition.py +481 -0
  25. kiwime_store-0.1.0/src/kiwime_store/skill_fs_mirror.py +134 -0
  26. kiwime_store-0.1.0/src/kiwime_store/vector.py +178 -0
  27. kiwime_store-0.1.0/tests/__init__.py +0 -0
  28. kiwime_store-0.1.0/tests/conftest.py +27 -0
  29. kiwime_store-0.1.0/tests/test_blob.py +58 -0
  30. kiwime_store-0.1.0/tests/test_custom_mcp.py +334 -0
  31. kiwime_store-0.1.0/tests/test_db.py +83 -0
  32. kiwime_store-0.1.0/tests/test_provider.py +86 -0
  33. kiwime_store-0.1.0/tests/test_repo.py +610 -0
  34. kiwime_store-0.1.0/tests/test_server.py +210 -0
  35. kiwime_store-0.1.0/tests/test_skill.py +155 -0
  36. kiwime_store-0.1.0/tests/test_skill_definition.py +276 -0
  37. kiwime_store-0.1.0/tests/test_skill_fs_mirror.py +109 -0
  38. 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);